Lesson 189: Year-End Partner Risk Letter Packet Assembler (2026)
Direct answer: December 2026 planning decks expect a single evidence ZIP for the year-opening risk letter—not three separate exports. Lesson 189 assembles Lesson 173 trend-board snapshot hash, Lesson 172 weighted mock-audit rollup, open deficiency counts, and Lesson 187 bind manifest index into year_end_risk_packet_manifest.json with a bundle SHA-256 and partner Markdown cover sheet.

Why this matters now (December 2026 → January 2027 replay)
Year-opening partner templates shifted in 2026:
- 2024: “Did you pass the latest mock?”
- 2025: “Show the last three
mock_audit_logrows.” - 2026–2027: “Which dimension stays structural-red? What hardening shipped? Prove AI findings had ticket IDs.”
Teams that email trend CSV + tabletop PDF + red-team annex separately get yellow-flagged in January replays. One ZIP, one manifest, one hash.
Lesson objectives
You will implement:
year_end_risk_packet_runjob metadatayear_end_risk_packet_manifest.jsoncontract- Deterministic assembler (ordered file list + hashes)
- Partner-facing
YEAR_END_RISK_COVER.md - Publish-gate
year_end_packet_incomplete
Prerequisites
- Lesson 173 —
deficiency_trend_board+ snapshot hash export - Lesson 172 — weighted pass rollup + open deficiency counts
- Lesson 187 —
red_team_bind_manifest.jsonindex - Lesson 188 — optional
CLOSURE_RECEIPT.jsonfor sprint proof - Lesson 170 — FAQ-bound readback structure for cover sheet tone
Required bundle members
| # | Artifact | Source lesson | Required |
|---|---|---|---|
| 1 | trend_board_snapshot.json + SHA-256 |
173 | Yes |
| 2 | mock_audit_rollup_v1.json |
172 | Yes |
| 3 | open_deficiency_summary.csv |
172 tickets | Yes |
| 4 | red_team_bind_manifest.json |
187 | Yes |
| 5 | closure_receipt_latest.json |
188 | If sprint closed |
| 6 | YEAR_END_RISK_COVER.md |
This lesson | Yes |
Folder: release-evidence/06-year-end/YYYY/
year_end_risk_packet_manifest.json
{
"schema": "year_end_risk_packet_manifest_v1",
"fiscal_year": 2026,
"cert_window_ids": ["q3_2026_steam_autumn", "q4_2026_meta_holiday"],
"assembled_at_utc": "2026-12-12T18:00:00Z",
"assembled_by_email": "[email protected]",
"members": [
{
"path": "trend_board_snapshot.json",
"sha256": "a1b2…",
"source_lesson": 173
},
{
"path": "mock_audit_rollup_v1.json",
"sha256": "c3d4…",
"source_lesson": 172
},
{
"path": "open_deficiency_summary.csv",
"sha256": "e5f6…",
"source_lesson": 172
},
{
"path": "red_team_bind_manifest.json",
"sha256": "7890…",
"source_lesson": 187
}
],
"bundle_sha256": "full_zip_hex…",
"structural_red_count": 2,
"open_deficiency_count": 7,
"weighted_pass_rate_pct": 91.4
}
Rule: members[].sha256 must match on-disk files at zip time; assembler recomputes and fail-closes on mismatch.
mock_audit_rollup_v1.json (assembler output)
{
"schema": "mock_audit_rollup_v1",
"windows": [
{
"cert_window_id": "q3_2026_steam_autumn",
"rehearsal_count": 2,
"weighted_pass_rate_pct": 92.1,
"open_deficiencies": 3,
"worst_dimension": 3,
"failure_mode_top_tag": "footer_schema_semver_drift"
}
],
"rollup_generated_at_utc": "2026-12-12T17:55:00Z"
}
Pull from mock_audit_log + mock_audit_deficiency_ticket (Lesson 172 schema).
Assembler sketch
def assemble_year_end_packet(year: int, window_ids: list[str]) -> Path:
members = []
members.append(collect_trend_board_snapshot()) # Lesson 173
members.append(build_mock_audit_rollup(window_ids)) # Lesson 172
members.append(export_open_deficiency_csv(window_ids))
members.append(copy_latest_bind_manifest()) # Lesson 187
optional = copy_latest_closure_receipt() # Lesson 188
if optional:
members.append(optional)
cover = render_year_end_cover_md(members)
members.append(cover)
verify_hashes(members)
zip_path = write_zip(members, out_dir / f"year_end_risk_{year}.zip")
manifest = build_manifest(members, zip_path)
write_json(out_dir / "year_end_risk_packet_manifest.json", manifest)
return zip_path
Partner cover sheet (YEAR_END_RISK_COVER.md)
Minimum sections (FAQ-bound tone per Lesson 170):
- Executive summary — 3 bullets: structural-red count, pass-rate trend, open deficiency delta vs prior quarter.
- Top structural-red rows — copy top 5 from trend board (dimension + tag + window count).
- Hardening shipped — reference Lesson 188 closure receipt or sprint budget mix.
- AI governance — count of binds in manifest; zero orphan promoted findings.
- Ask of partner — explicit list of decisions needed in January window.
No free-text metrics without manifest cross-reference.
Publish gate
-- block_reason = 'year_end_packet_incomplete'
WHEN :active_cert_window IN (
SELECT unnest(cert_window_ids)
FROM year_end_risk_packet_run
WHERE fiscal_year = :current_fiscal_year
AND bundle_sha256 IS NULL
)
OR NOT EXISTS (
SELECT 1 FROM year_end_risk_packet_run r
WHERE r.fiscal_year = :current_fiscal_year
AND r.status = 'assembled'
);
Clears when manifest + zip exist and members length ≥ 4 required paths.
Common mistakes
- Three separate emails instead of one ZIP — replay failure.
- Trend snapshot from live view, not pinned hash — reviewer cannot reproduce rows.
- Omitting bind manifest when AI annex shipped — automatic yellow flag.
bundle_sha256over manifest only (wrong) — hash the zip bytes.- Cover sheet claims pass rate not in rollup JSON — inconsistency fail.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Gate year_end_packet_incomplete |
Missing member file | Re-run assembler checklist |
| Hash mismatch on member | File edited after hash | Regenerate from source jobs |
| Partner rejects ZIP | Wrong fiscal year folder | Align cert_window_ids |
| Rollup empty | No scored logs | Complete Lesson 182 path |
| Bind manifest stale | Old publish_tuple_hash |
Regenerate Lesson 187 export |
Verification checklist
- [ ] Zip contains all four required members
- [ ] Manifest
bundle_sha256matches zip on disk - [ ] Cover sheet numbers match manifest counters
- [ ] Trend snapshot hash matches Lesson 173 export log
- [ ] Publish gate clears only after
assembledstatus
Mini exercise (30 minutes)
- Export sample trend snapshot + rollup for two cert windows.
- Add bind manifest with two binds.
- Run assembler; record
bundle_sha256. - Deliberately rename one file inside zip—confirm verifier fails.
- Draft cover sheet ≤ 1 page using manifest counters only.
Continuity
- Lesson 173 — board snapshot source.
- Lesson 172 — scoring and deficiencies.
- Lesson 187 — bind index.
- Lesson 188 — optional closure proof.
- Next: Lesson 190 — nightly bind reconciliation and orphan ticket sweep (
BIND_RECONCILE_RECEIPT.json). - Blog: 30-minute weekly indie studio operating review — cadence that feeds year-end assembly.
- Resource: Q3 mock audit packet templates — folder layout baseline.
FAQ
When should we assemble?
First week of December for calendar-year partners; refresh after final Q4 tabletop.
Can we include WORM archive pointers?
Yes as optional member #7 from Lesson 175 when January off-cycle questions are likely.
Does this replace executive readback?
No. It packages evidence for the year-opening letter; Lesson 170 readback cadence still runs weekly.
ZIP password?
Use partner-provided SFTP or encrypted link per contract—never email password in same thread as manifest.
December 2026 partners reward studios that treat the year-opening letter like a release artifact: one manifest, one hash, reproducible rows. Assemble once, attach once, and enter January replays with the packet already open on both sides.