# Plan: PDF Export for the AI Assurance Audit (opt-in)

## Scope

One report family: the AI Assurance audit. One trigger: explicit user consent. ASUCB generates the PDF only after asking and getting a yes, the same way Step 5 already offers the JSON scorecard. No other report path changes. No auto-generation.

## Why this is small

`scripts/report_pdf_exporter.py` already renders HTML to PDF through Playwright (optional dependency, lazy import, consent install). It has tests. Reuse it as-is.

The single-course assurance report has no HTML form today. The skill presents it inline in chat (SKILL.md Step 4) and offers a raw JSON scorecard (Step 5). So the only new piece is a renderer that turns the scorecard plus Claude's inline findings into a styled HTML page, which the exporter then prints to PDF.

## Design

Render, then export, both behind a consent prompt.

1. **Renderer** — `scripts/assurance_report_html.py`. Input: the scorecard JSON that Step 5 already produces. Optional second input: a narrative sidecar holding the parts Claude generates inline (substitutability findings, AIAS level, authenticity 5DF, data caveats, recommendations). Output: a self-contained HTML report using the editorial print CSS proved out on the Desktop playbook.
   - Deterministic sections always render from the scorecard: tier, assurance coverage, lane balance, Dimension D/A/C/E, AI-Forward D1-D6, flags, Bloom calibration findings, durable skills, assumptions.
   - Narrative sections render when the sidecar is present. Absent sidecar still yields a complete deterministic report.
   - Data caveats lead the document. The STP530 run showed why: zero grade weights, polluted inventory, empty syllabus body, lane heuristic over-crediting peer review. A report that buries those misleads.

2. **Export** — pass the rendered HTML to `report_pdf_exporter.export_pdf`, output to `courses/{courseId}/reports/{YYYYMMDD-HHmmss}-assurance-audit.pdf`.

3. **Consent** — add a Step 5 sibling. After the report, ask: "Want a PDF of this assurance report?" Generate only on an explicit yes. If Playwright is missing, show the install prompt and stop. Never install or render without the yes.

## Tasks

Each task is one Sonnet PR. Every change runs stop-slop on prose and docstrings and karpathy on code: minimal diff, reuse the exporter, no new dependency, touch only what the task names.

### T1 — Renderer + template
Build `scripts/assurance_report_html.py` and a shared template (extend `templates/report-html/report-base.html` or add an assurance partial). Render a fixture scorecard to HTML.
**Acceptance:**
- Renders a complete report from a scorecard JSON with no sidecar.
- Merges the narrative sidecar when given.
- Data-caveats section renders first when caveats are present.
- Unit test renders a fixture scorecard and asserts each deterministic section and the caveat ordering. Verify one real PDF in a browser before claiming done.

### T2 — Wire the consent step into the skill
Update `skills/assurance-audit/SKILL.md` Step 5: add the PDF offer next to the JSON offer. Document the flow (export JSON or build sidecar, call the renderer, call `export_pdf`, save to the reports path). State the consent rule and the Playwright-missing behavior.
**Acceptance:** SKILL.md describes the opt-in PDF path; the consent rule and the optional-dependency fallback are explicit; no auto-generation anywhere in the text.

### T3 — Docs + release (Opus)
Update `CHANGELOG.md` and any CLAUDE.md report-storage note. Cut the release: `VERSION` plus both manifests plus tag, matching the four-things rule.

## Consent contract

- Ask first, every time. Generate only on an explicit yes.
- Default stays JSON and inline. PDF is the extra step, never the default.
- Playwright absent: print the install prompt, stop, do not partially produce.

## Workflow

- **Plan + review:** Opus (this session).
- **Code:** Sonnet 4.6 subagents, one per task, each on its own task-scoped branch.
- **Gates:** every PR through `scripts/agent_review_and_merge.py` with a reviewer that is not the coder; four gates (mergeable, CI green, full pytest on a clean checkout, reviewer sign-off).
- **Order:** T1, then T2, then T3.

## Risks

- **Playwright stays optional.** The base install never gains a hard dependency. `--pdf` without Playwright prints the consent-install prompt and exits clean.
- **The PDF must match the inline report.** The narrative sidecar carries Claude's judgments and caveats so the document reflects what the user saw, not only the deterministic scorecard.
- **One renderer.** The exporter is the only mechanism. The headless-Chrome path from the Desktop playbook does not enter the plugin.
- **Scope holds.** This covers the assurance audit alone. Other report families keep their current HTML-only output.
