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.

Lesson hero for year-end partner risk letter packet assembler

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_log rows.”
  • 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_run job metadata
  • year_end_risk_packet_manifest.json contract
  • Deterministic assembler (ordered file list + hashes)
  • Partner-facing YEAR_END_RISK_COVER.md
  • Publish-gate year_end_packet_incomplete

Prerequisites

  • Lesson 173deficiency_trend_board + snapshot hash export
  • Lesson 172 — weighted pass rollup + open deficiency counts
  • Lesson 187red_team_bind_manifest.json index
  • Lesson 188 — optional CLOSURE_RECEIPT.json for 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):

  1. Executive summary — 3 bullets: structural-red count, pass-rate trend, open deficiency delta vs prior quarter.
  2. Top structural-red rows — copy top 5 from trend board (dimension + tag + window count).
  3. Hardening shipped — reference Lesson 188 closure receipt or sprint budget mix.
  4. AI governance — count of binds in manifest; zero orphan promoted findings.
  5. 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_sha256 over 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_sha256 matches zip on disk
  • [ ] Cover sheet numbers match manifest counters
  • [ ] Trend snapshot hash matches Lesson 173 export log
  • [ ] Publish gate clears only after assembled status

Mini exercise (30 minutes)

  1. Export sample trend snapshot + rollup for two cert windows.
  2. Add bind manifest with two binds.
  3. Run assembler; record bundle_sha256.
  4. Deliberately rename one file inside zip—confirm verifier fails.
  5. Draft cover sheet ≤ 1 page using manifest counters only.

Continuity

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.