test(P5): ci + tests + readme for single-doc dual-pptx pipeline (REQ-273,274,275)

CI workflows: install python-pptx, pin CLI versions, stage both PPTX +
inlined HTML. test_slides_pipeline.py: inverted theme assertion (now
default+inline), deleted source-md tests, added 8 new tests
(penetrate absence, image inlining, python-pptx, benefit class, single
source, speaker-notes comments, default theme, css retained). New
test_pptx_generator.py: slide count, title colors, slide titles, table
rendering, image embedding, benefit callout. README rewritten for 3-step
single-document + dual-PPTX + image-inlining pipeline.

---ci---
project: acdl
phase: 5
milestone: v1.23
status: execute
phase_role: execution
---/ci---
This commit is contained in:
Jon Chery
2026-08-12 00:34:23 +00:00
parent 9b8ab75b85
commit e07a210c70
6 changed files with 726 additions and 406 deletions
+177 -145
View File
@@ -2,147 +2,184 @@
Leadership-facing presentation decks for the Nova platform.
## The 4-step slide creation process
## The 3-step slide creation process
Every presentation in this folder is produced by the same four-step process.
**Never edit the Marp deck, the PPTX, or the talking points directly**
always start from the full markdown source of truth (Step 1), synthesize the
Marp deck (Step 2), export to HTML + PPTX (Step 3), then distill the talking
points (Step 4). This keeps a reviewable, plain-text source of truth for
every deck and a presenter-ready cue sheet for delivery.
Every presentation in this folder is produced by the same three-step
process. **Never edit the rendered HTML, either PPTX, or the talking
points directly** — always start from the Marp deck source of truth
(Step 1), render it (Step 2), then distill the talking points (Step 3).
This keeps a reviewable, plain-text source of truth for every deck and a
presenter-ready cue sheet for delivery.
```
Step 1: full markdown Step 2: Marp deck Step 3: HTML + PPTX Step 4: Talking points
(source of truth) ──► (lean, 21 slides) ──► (rendered) ──► (presenter cues)
*.md *-marp.md *.html / *.pptx *-talking-points.md
+ speaker notes + embedded PNG diagrams + 3-6 bullets per slide
+ mermaid code blocks + Marp frontmatter + key takeaway per slide
+ no speaker notes + indexed by Marp slide #
+ no maturity badges + content distilled from Step 1
+ no version in footer
Step 1: Author the deck Step 2: Render Step 3: Talking points
(source of truth) ──► (HTML + dual PPTX) ──► (presenter cues)
*-marp.md *.html *-talking-points.md
+ ## Slide N — Title + mermaid PNGs + 3-6 bullets per slide
+ <!-- Speaker notes: --> + MARP PPTX (image-of-slide) + key takeaway per slide
+ <!-- Talking points: --> + python PPTX (structured) + indexed by slide #
+ <div class="benefit"> + base64-inlined HTML + content distilled from
+ embedded PNG diagrams (self-contained) the Marp deck
```
### Step 1 — Full markdown (source of truth)
### Step 1 — Author the deck (source of truth)
**File convention:** `<deck-name>.md` (e.g. `nova-autonomous-cloud-delivery.md`).
**File convention:** `<deck-name>-marp.md` (e.g.
`nova-autonomous-cloud-delivery-marp.md`).
Write the complete deck as a standard markdown file. This is the **source of
truth** — it contains:
This is the **sole source of truth** — the Marp deck that is both authored
and rendered. It contains:
- Every slide as an `## Slide N — Title` H2 section.
- **Marp frontmatter** at the top: `marp: true`, `theme: default`,
`paginate: true`, `size: 16x9`, a header/footer, and an inline `style:`
block carrying the S&P palette (`#D6002A` red, `#1B1B1B` black, the
`section.title` rule). The styling is **inline** — no standalone theme
CSS is loaded at render time.
- Every slide as an `## Slide N — Title` (or `## Appendix A1 — Title`) H2
section. The H1 title slide precedes slide 1.
- Tight bullets with leadership-relevant content.
- A `> **Speaker notes:**` block at the end of each slide with the nuance,
the "who cares and why," and the honesty caveats.
- Mermaid diagrams as ```` ```mermaid ```` fenced code blocks (these render
on GitHub/Pages but not in Marp — Step 2 converts them to images).
- An honest "shipped vs. deferred" framing: every "available today" claim is
grounded in shipped/verified work; every "deferred" item is explicitly
- **Speaker notes** as `<!-- Speaker notes: ... -->` HTML comments at the
end of each slide. Marp excludes HTML comments from the rendered slide;
they are for authors/presenters only.
- **Talking points** as `<!-- Talking points: ... -->` HTML comments (also
excluded from rendering — Step 3 mirrors them into a standalone cue
sheet).
- **Benefit callouts** as `<div class="benefit">...</div>` (styled by the
inline `style:` block — italic, S&P-red top border). No `**Benefit:**`
text prefixes.
- Mermaid diagrams **pre-rendered to PNG** under `assets/png/` and embedded
with `![w:1000](assets/png/<name>.png)` (or `h:480 class:tall` for tall
images). The `.mmd` sources live under `assets/mmd/`.
- **No maturity badges**, **no version in the footer**, **no internal
decision/requirement IDs or `.py` file paths** in the slide bodies
(those live in the `.ciagent/` files only; speaker-note HTML comments are
exempt).
- An honest "shipped vs. deferred" framing: every "available today" claim
is grounded in shipped/verified work; every "deferred" item is explicitly
marked with the blocking work in plain language.
**Why this file is the source of truth:** it is reviewable in any markdown
viewer, diffs cleanly in git, and carries the full reasoning (speaker notes)
that a presenter needs. The Marp deck and PPTX are *derived artifacts* — if a
fact is wrong, fix it here and re-run Steps 2 and 3.
**Why the Marp deck is the source of truth:** it is reviewable in any
markdown viewer, diffs cleanly in git, and carries the full reasoning
(speaker notes) that a presenter needs. The HTML and PPTX are *derived
artifacts* — if a fact is wrong, fix it here and re-run Step 2.
### Step 2 — Marp deck synthesis
> **`nova-sp-theme.css` is RETIRED from render.** The standalone theme
> stylesheet under `assets/nova-sp-theme.css` is kept as a **reference
> only** and is **not loaded at render time**. The live styling is the
> inline `style:` block in the `-marp.md` frontmatter. Do NOT pass the CSS
> via `--theme`; it is not in the render path.
**File convention:** `<deck-name>-marp.md` (e.g. `nova-autonomous-cloud-delivery-marp.md`).
### Step 2 — Render (HTML + dual PPTX)
Synthesize the full markdown into a lean Marp deck:
`bash scripts/render_slides.sh [deck-name]` renders the Marp deck
end-to-end:
- **Marp frontmatter** at the top: `marp: true`, `theme: nova-sp`,
`paginate: true`, `size: 16x9`, a header/footer, and an inline `style:`
block for fonts, colors, tables.
- **No speaker notes.** The Marp deck is what the audience sees; the
speaker notes live only in the Step 1 source of truth.
- **Mermaid diagrams → PNG images.** Marp does not render mermaid fenced
blocks natively. Extract each mermaid block from Step 1 into a `.mmd`
source file under `assets/mmd/`, render it to PNG under `assets/png/`,
and embed it with `![w:1000](assets/png/<name>.png)`.
- **`<!-- _class: title -->` + `<!-- _paginate: false -->`** on title and
closing slides for the dark-background title style.
- **No maturity badges.** The deck no longer uses `<span class="badge">`
spans. Deferred items are named in plain language with their blocking
work, not tagged with a badge.
- **No version in the footer.** The footer carries the deck title only.
- **Tighter prose** than Step 1 — strip the speaker-note nuance; keep the
leadership-relevant selling points.
### Step 3 — Render to HTML and PPTX
Both formats are derived from the Marp deck. **HTML is committed to the repo**
(viewable in any browser, self-contained with base64-embedded images). **PPTX
is also committed to the repo** as a first-class binary artifact and is
attached to the phase's release via `scripts/attach_release_asset.py`.
1. **Mermaid PNGs** — each `assets/mmd/*.mmd``assets/png/*.png`
(S&P-themed via `sp-theme.json`, 2x scale, transparent background).
2. **MARP HTML**`*-marp.md``*.html` (S&P inline style, Marp default
theme). Pinned `@marp-team/marp-cli@4.5.0`.
3. **MARP PPTX**`*-marp.md``*.pptx` (image-of-slide PPTX; the primary
release attachment).
4. **Inline images**`scripts/inline_images.py` rewrites the HTML to
base64-embed every `assets/` image so the HTML is self-contained (no
external asset folder needed for redistribution).
5. **python PPTX**`scripts/render_pptx.py` produces a second,
structured, editable PPTX (`*-python.pptx`) with native text boxes,
native tables, embedded pictures, and italic benefit callouts.
6. **Stage** — all rendered artifacts (PNGs + HTML + both PPTX) are
`git add`-ed for commit.
```bash
CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @marp-team/marp-cli@latest --allow-local-files \
docs/presentations/<deck-name>-marp.md \
-o docs/presentations/<deck-name>.html
bash scripts/render_slides.sh nova-autonomous-cloud-delivery
```
HTML export inlines images as base64 data URIs. PPTX export requires
`--allow-local-files` so the local PNG diagrams are embedded in the file.
The render + commit + attach pipeline is automated by `scripts/render_slides.sh`.
Both the HTML and both PPTX files are committed to the repo; the MARP
PPTX is also attached to the phase's release via
`scripts/attach_release_asset.py`.
### Step 4 — Talking points (presenter cues)
#### Dual-PPTX output
| PPTX | File | Render | Purpose |
|---|---|---|---|
| **MARP PPTX** | `*.pptx` | `@marp-team/marp-cli` (Chrome screenshot of each slide) | Image-of-slide; the primary release attachment (pixel-perfect, not editable) |
| **python PPTX** | `*-python.pptx` | `scripts/render_pptx.py` (python-pptx) | Structured, editable PPTX (native text boxes, tables, pictures) for comparison/editing |
### Step 3 — Talking points (presenter cues)
**File convention:** `<deck-name>-talking-points.md` (e.g.
`nova-autonomous-cloud-delivery-talking-points.md`).
Distill the source of truth (Step 1) into presenter-ready cues, indexed by
the Marp deck (Step 2) slide structure:
Distill the deck's `<!-- Talking points: -->` HTML comments into
presenter-ready cues, indexed by the Marp deck (Step 1) slide structure:
- **One section per Marp slide** — `## Slide N — Title`, matching the Marp
deck's 20 main + 1 appendix slide structure exactly.
- **3-6 talking point bullets per slide** — punchy, actionable cues distilled
from the source markdown's speaker notes.
- **3-6 talking point bullets per slide** — punchy, actionable cues
distilled from the Marp deck's `<!-- Talking points: -->` comments.
- **Key takeaway per slide** — the one memorable thing the audience should
walk away with from that slide.
- **No content duplication** — the talking points reference the Marp slides
for visual context and the source markdown for full detail.
- **No content duplication** — the talking points reference the Marp
slides for visual context.
## Directory layout
```
docs/presentations/
├── README.md ← this file
├── nova-autonomous-cloud-delivery.md ← Step 1: full source of truth (20 main slides + speaker notes)
├── nova-autonomous-cloud-delivery-marp.md ← Step 2: Marp deck (20 main + 1 appendix = 21 slides)
├── nova-autonomous-cloud-delivery.html ← Step 3: rendered HTML (committed, S&P-themed)
├── nova-autonomous-cloud-delivery.pptx ← Step 3: rendered PPTX (committed, S&P-themed)
├── nova-autonomous-cloud-delivery-talking-points.md ← Step 4: presenter cues (19 sections)
├── nova-autonomous-cloud-delivery-marp.md ← Step 1: sole source of truth (title + 20 main + 1 appendix = 22 slides + speaker notes + talking points)
├── nova-autonomous-cloud-delivery.html ← Step 2: rendered HTML (committed, S&P inline style, base64-inlined images)
├── nova-autonomous-cloud-delivery.pptx ← Step 2: MARP PPTX (image-of-slide, primary release attachment)
├── nova-autonomous-cloud-delivery-python.pptx ← Step 2: python-pptx (structured, editable)
├── nova-autonomous-cloud-delivery-talking-points.md ← Step 3: presenter cues (21 sections)
└── assets/
├── nova-sp-theme.css ← S&P Global Energy Marp theme (all slide chrome)
├── nova-sp-theme.css ← RETIRED from render — reference only (not loaded; live styling is the inline `style:` block)
├── puppeteer-config.json ← no-sandbox config for mmdc
├── mmd/ ← mermaid source files (Step 2 input)
│ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile)
├── mmd/ ← mermaid source files (Step 2 input)
│ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile)
│ └── ... (per-slide .mmd files)
└── png/ ← rendered mermaid PNGs (committed, S&P-themed)
└── png/ ← rendered mermaid PNGs (committed, S&P-themed, 2x, transparent)
```
## Tooling & scripts
| Script | Purpose |
|---|---|
| `scripts/render_slides.sh` | End-to-end render: mermaid PNGs → MARP HTML + PPTX → base64-inlined HTML → python-pptx PPTX → stage all artifacts. Pinned `@marp-team/marp-cli@4.5.0` + `@mermaid-js/mermaid-cli@11.16.0`. |
| `scripts/inline_images.py` | Rewrites the rendered HTML to base64-embed every `assets/` image (self-contained HTML for redistribution). |
| `scripts/render_pptx.py` | Produces the structured, editable `*-python.pptx` (native text boxes, tables, pictures, italic benefit callouts) via `python-pptx`. |
| `scripts/attach_release_asset.py` | Attaches the MARP PPTX to the phase's release. |
| Dependency | Where declared | Purpose |
|---|---|---|
| `@marp-team/marp-cli@4.5.0` | `scripts/render_slides.sh` (pinned) | Marp → HTML + PPTX |
| `@mermaid-js/mermaid-cli@11.16.0` | `scripts/render_slides.sh` (pinned) | Mermaid → PNG |
| `python-pptx>=0.6.23` | `pyproject.toml` `[project.optional-dependencies] slides` | Structured PPTX (`pip install -e ".[slides]"`) |
## Conventions
### Appendix structure
### Slide structure
Each Marp deck has **20 main slides + 1 appendix slide**. The main 20 are the
presentation; the appendix is for Q&A backup. (v1.22 split slides 3 and 8
to relieve overflow, increasing the count from 18 to 20.)
Each Marp deck has **1 title slide + 20 main slides + 1 appendix slide = 22
rendered slides** (21 `## ` sections + the H1 title slide). The main 20
are the presentation; the appendix is for Q&A backup. (v1.22 split slides
3 and 8 to relieve overflow, increasing the main count from 18 to 20.)
- **Title slide** (H1): `<!-- _class: title -->` + `<!-- _paginate: false -->`
for the dark-background title style (S&P-red top border on black).
- **Main slides** (1-20): the story arc — Problem → Solution → Proof →
Roadmap + Ask. These are what the audience sees during the talk.
- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference for
Q&A.
- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference
for Q&A.
### Honesty framing
Every capability claim in the deck is grounded, derived, or honestly
deferred with its blocking work named in plain language. Internal provenance
(decision IDs, requirement IDs, internal file paths) is kept out of the
audience-facing slides — those live in the `.ciagent/` files only. When in
doubt, check `.ciagent/ROADMAP.md` and the milestone status in
`.ciagent/PROJECT.md`.
deferred with its blocking work named in plain language. Internal
provenance (decision IDs, requirement IDs, internal file paths) is kept
out of the audience-facing slide bodies — those live in the `.ciagent/`
files only (and may appear inside `<!-- ... -->` speaker-note comments,
which Marp excludes from the rendered slide). When in doubt, check
`.ciagent/ROADMAP.md` and the milestone status in `.ciagent/PROJECT.md`.
### Audience
@@ -156,76 +193,70 @@ Head of Infrastructure, Head of DevOps. The framing rules:
outcome; the mechanism follows.
- **Security, remediation velocity, reliability, lead time, observability,
citizen developer** are the themes — not implementation details.
- **"Infrastructure operations become visible"** is the recurring theme across
the deck.
- **"Infrastructure operations become visible"** is the recurring theme
across the deck.
### Diagrams
Mermaid diagrams in the Step 1 source use the repo's existing `flowchart`
style (renders on GitHub/Pages). For the Marp deck (Step 2):
Mermaid diagrams are authored as `assets/mmd/*.mmd` source files and
rendered to PNG under `assets/png/`:
1. Extract the mermaid block into `assets/mmd/<deck>-<slide>-<name>.mmd`.
1. Author the mermaid block as `assets/mmd/<deck>-<slide>-<name>.mmd`.
2. Use **horizontal layouts** (`flowchart LR`) or **subgraph row-wrapping**
for wide diagrams so the PNG fits a 16:9 slide without shrinking to
illegibility.
3. Render with a 2x scale factor and transparent background for crisp slides.
4. Embed with `![w:1000](assets/png/<name>.png)` (or `h:320` for tall images).
3. Render with a 2x scale factor and transparent background for crisp
slides (`scripts/render_slides.sh` does this with the S&P theme JSON).
4. Embed with `![w:1000](assets/png/<name>.png)` (or `h:480 class:tall`
for tall images).
5. The render pipeline base64-inlines the PNGs into the committed HTML so
the HTML is self-contained.
## Build commands
### Prerequisites
- Node.js + npx (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`)
- A Chrome/Chromium binary (Marp PPTX export requires it)
- **Node.js + npx** (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`)
- **A Chrome/Chromium binary** (Marp PPTX export requires it)
- **Python 3.10+** with the `slides` extra: `pip install -e ".[slides]"`
(installs `python-pptx>=0.6.23`)
This environment has a working Chromium at:
`/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome`
### Render all mermaid diagrams to PNG
```bash
cd docs/presentations/assets
for f in mmd/*.mmd; do
name=$(basename "$f" .mmd)
PUPPETEER_EXECUTABLE_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @mermaid-js/mermaid-cli@latest \
-i "$f" -o "png/$name.png" \
-p puppeteer-config.json -s 2 -b transparent \
--configFile mmd/sp-theme.json
done
```
### Render a Marp deck to HTML + PPTX (committed artifacts)
### Render the deck (HTML + dual PPTX + inlined images)
```bash
bash scripts/render_slides.sh nova-autonomous-cloud-delivery
```
This renders all mermaid PNGs, the HTML, and the PPTX, and stages them for
commit. The `--allow-local-files` flag is required so local PNG diagrams are
embedded. Both HTML and PPTX are committed to the repo; the PPTX is also
This renders all mermaid PNGs, the HTML (with base64-inlined images), the
MARP PPTX, and the python-pptx PPTX, and stages them for commit. Both
HTML and both PPTX files are committed to the repo; the MARP PPTX is also
attached to the phase's release.
## Adding a new presentation
1. **Write the full markdown** as `<deck-name>.md` following the
`## Slide N — Title` + `> **Speaker notes:**` structure. This is the
source of truth.
2. **Extract any mermaid diagrams** into `assets/mmd/<deck-name>-<slide>-<name>.mmd`
and render them to `assets/png/` (command above).
3. **Synthesize the Marp deck** as `<deck-name>-marp.md` with frontmatter,
no speaker notes, embedded PNGs, and no badges.
4. **Render to HTML + PPTX** via `scripts/render_slides.sh <deck-name>` and
commit both to `docs/presentations/`.
5. **Distill the talking points** as `<deck-name>-talking-points.md` — one
section per Marp slide, 3-6 talking point bullets + key takeaway, content
distilled from the source markdown (Step 1), indexed by the Marp deck
(Step 2) slide structure.
6. **Verify** the PPTX slide count and that media files are embedded:
1. **Author the Marp deck** as `<deck-name>-marp.md` — frontmatter
(`marp: true`, `theme: default`, `paginate: true`, `size: 16x9`, an
inline `style:` block with the S&P palette), `## Slide N — Title`
sections, `<!-- Speaker notes: -->` + `<!-- Talking points: -->` HTML
comments, and `<div class="benefit">` callouts. This is the sole source
of truth.
2. **Author any mermaid diagrams** as `assets/mmd/<deck-name>-<slide>-<name>.mmd`
(Step 2 renders them to `assets/png/`).
3. **Render** via `bash scripts/render_slides.sh <deck-name>` — this
produces the HTML (base64-inlined), the MARP PPTX, and the python-pptx
PPTX, and stages all of them (plus the PNGs) for commit.
4. **Distill the talking points** as `<deck-name>-talking-points.md` — one
section per Marp slide, 3-6 talking point bullets + key takeaway,
content distilled from the Marp deck's `<!-- Talking points: -->`
comments, indexed by the Marp deck slide structure.
5. **Verify** the PPTX slide count and that media files are embedded:
```bash
python3 -c "
import zipfile, re
with zipfile.ZipFile('<output>.pptx') as z:
with zipfile.ZipFile('docs/presentations/<deck-name>.pptx') as z:
slides = [n for n in z.namelist() if re.match(r'ppt/slides/slide\d+\.xml$', n)]
media = [n for n in z.namelist() if n.startswith('ppt/media/')]
print(f'{len(slides)} slides, {len(media)} media files')
@@ -234,16 +265,17 @@ attached to the phase's release.
## Current decks
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience |
|---|---|---|---|---|---|---|
| Nova — The Autonomous Cloud Delivery Platform | `nova-autonomous-cloud-delivery.md` | `nova-autonomous-cloud-delivery-marp.md` | `nova-autonomous-cloud-delivery.html` + `.pptx` (committed + release-attached) | `nova-autonomous-cloud-delivery-talking-points.md` | 20 main + 1 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
| Deck | Source of truth (Step 1) | Rendered HTML + dual PPTX (Step 2) | Talking points (Step 3) | Slides | Audience |
|---|---|---|---|---|---|
| Nova — The Autonomous Cloud Delivery Platform | `nova-autonomous-cloud-delivery-marp.md` | `nova-autonomous-cloud-delivery.html` (inlined) + `nova-autonomous-cloud-delivery.pptx` (MARP, release-attached) + `nova-autonomous-cloud-delivery-python.pptx` (structured) | `nova-autonomous-cloud-delivery-talking-points.md` | title + 20 main + 1 appendix (22) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
> **v1.21:** the deck was renamed from "No-Humans Infrastructure Platform"
> to "Autonomous Cloud Delivery Platform" (professional framing; conveys
> autonomy without the provocative wording). The narrative restructured to
> a 4-beat arc (Problem → Solution → Proof → Roadmap + Ask). Internal
> provenance (decision IDs, requirement IDs, file paths) removed from
> audience-facing slides. Maturity badges removed. The RACI matrix expanded
> to four roles (Quality Engineering + SRE). The Atelier slide split into
> two. The pipeline hardened: Checkov on static code before the plan;
> Wiz-or-Checkov on the plan (never both).
> **v1.23:** the slide creation process collapsed from 4 steps to 3 — the
> plain `<deck-name>.md` was deleted; `<deck-name>-marp.md` is now the
> sole source of truth. The standalone `nova-sp-theme.css` was retired
> from render (the live styling is the inline `style:` block in the
> `-marp.md` frontmatter; the CSS file is retained as a reference only).
> Speaker notes moved from blockquotes into `<!-- Speaker notes: -->`
> HTML comments. Benefit callouts moved from `**Benefit:**` prefixes to
> `<div class="benefit">`. The render pipeline now produces a dual-PPTX
> output (MARP image-of-slide + python-pptx structured) and base64-inlines
> all images into the committed HTML.