Lesson 188: Sprint Hardening Closure Receipt Against Lesson 173 Budget Variance Gate (2026)
Direct answer: Lesson 183 feeds deficiencies into sprint_hardening_feed. Lesson 173 allocates the sprint budget. Lesson 188 requires a scribe-signed closure receipt per sprint proving closed tickets match that allocation within 25 percentage points—or documents variance in sprint_hardening_budget_variance_note before publish.

Why this matters now (2026 H2 closure proof)
Partners stopped accepting “we exported CSV” as sprint discipline:
- Lesson 183 ships rows; engineering closes tickets ad hoc.
- Lesson 173 Block 6 asks a yes/no: did work match budget?
- 2026 H2 readbacks request
CLOSURE_RECEIPT.jsonbeside the export SHA-256. - Silent drift (35% budget to Lesson 167, 0% closed there) yellow-flags January 2027 replays.
The receipt closes the loop: budget row → closed tickets → measured mix → signed attestation.
Lesson objectives
You will implement:
sprint_hardening_closure_receipt— one row per sprint close- Mechanical variance calculator (closed mix vs
sprint_hardening_budget) - Scribe-signed
CLOSURE_RECEIPT.jsonartifact - Publish-gate
hardening_closure_variance_open - Friday Block 6 yes/no metric wiring (Lesson 173)
Prerequisites
- Lesson 173 —
sprint_hardening_budget, Block 6 variance rule - Lesson 183 —
sprint_hardening_feedingest - Lesson 172 —
mock_audit_deficiency_ticketclosure statuses - Lesson 187 — optional
source_red_team_finding_idon closed rows
sprint_hardening_closure_receipt
CREATE TABLE sprint_hardening_closure_receipt (
receipt_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sprint_id TEXT NOT NULL,
budget_id UUID NOT NULL REFERENCES sprint_hardening_budget(budget_id),
cert_window_id TEXT NOT NULL,
closed_ticket_count INT NOT NULL CHECK (closed_ticket_count >= 0),
closed_mix_json JSONB NOT NULL, -- { "167": 0.32, "170": 0.28, ... }
budget_mix_json JSONB NOT NULL, -- snapshot from budget row at close
max_dimension_delta_pp NUMERIC(5,2) NOT NULL, -- percentage points
within_variance_gate BOOLEAN NOT NULL, -- max_delta <= 25
variance_note TEXT,
scribe_email TEXT NOT NULL,
signed_at_utc TIMESTAMPTZ NOT NULL DEFAULT now(),
receipt_sha256 TEXT NOT NULL,
UNIQUE (sprint_id)
);
Rule: within_variance_gate = false requires non-empty variance_note (same discipline as Lesson 173).
Variance calculator
For each source_lesson_id in the active budget:
def compute_closure_variance(
budget_mix: dict[str, float],
closed_mix: dict[str, float],
) -> tuple[float, bool]:
deltas = []
for lesson_id, budget_pct in budget_mix.items():
closed_pct = closed_mix.get(lesson_id, 0.0)
deltas.append(abs(closed_pct - budget_pct))
max_delta_pp = max(deltas) * 100.0
return max_delta_pp, max_delta_pp <= 25.0
Closed mix counts only tickets with status IN ('resolved', 'carved_out') in the sprint window, grouped by source_lesson_id from Lesson 183 feed rows.
CLOSURE_RECEIPT.json
{
"schema": "sprint_hardening_closure_receipt_v1",
"sprint_id": "2026-W42",
"cert_window_id": "q4_2026_steam_autumn",
"budget_id": "b7c2…",
"closed_ticket_count": 14,
"budget_mix": { "167": 0.35, "170": 0.25, "171": 0.15, "165": 0.10, "reserved": 0.15 },
"closed_mix": { "167": 0.29, "170": 0.36, "171": 0.14, "165": 0.06, "reserved": 0.15 },
"max_dimension_delta_pp": 11.0,
"within_variance_gate": true,
"variance_note": null,
"scribe_email": "[email protected]",
"signed_at_utc": "2026-10-17T16:05:00Z",
"receipt_sha256": "…"
}
Attach beside Lesson 183 deficiency_export_*.csv in release-evidence/05-operations/.
Publish gate extension
-- block_reason = 'hardening_closure_variance_open'
WHEN EXISTS (
SELECT 1 FROM sprint_hardening_closure_receipt r
WHERE r.cert_window_id = :active_cert_window
AND r.within_variance_gate = false
AND (r.variance_note IS NULL OR length(trim(r.variance_note)) < 20)
);
Also block when no receipt exists for the sprint that consumed the latest feed ingest (pair with export_run_id on feed metadata).
Friday Block 6 wiring
| Input | Source |
|---|---|
Current sprint_hardening_budget |
Lesson 173 table |
Latest CLOSURE_RECEIPT.json |
This lesson |
New structural-red this week |
deficiency_trend_board |
Yes/no metric (Lesson 173): within_variance_gate = true on the receipt for the sprint under review → answer y. Else n and paste variance_note into the ops doc.
Six-step sprint-close procedure
| Step | Owner | Action |
|---|---|---|
| 1 | Governance owner | Confirm feed ingest applied for sprint |
| 2 | Engineering | Close tickets; no status flips without ticket IDs |
| 3 | Scribe | Run variance job; review max_dimension_delta_pp |
| 4 | Governance owner | Approve or write variance_note if > 25 pp |
| 5 | Scribe | Sign CLOSURE_RECEIPT.json; SHA-256 to Slack |
| 6 | Engineering | Clear publish gate only when gate satisfied |
Target: complete within 30 minutes of Friday Block 6 start.
Common mistakes
- Counting
opentickets as closed — inflates falseyon Block 6. - Using live budget row after mid-sprint edits — snapshot
budget_mix_jsonat sprint start. - Empty
variance_notewhen over 25 pp — publish gate stays red correctly. - Receipt for wrong
cert_window_id— partner replay mismatch. - Skipping receipt when only one ticket closed — still required for audit trail.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Gate hardening_closure_variance_open |
Over 25 pp, no note | Write variance_note ≥ 20 chars |
Block 6 always n |
Closed mix uses wrong lesson IDs | Align with source_lesson_id in CSV |
| Receipt SHA mismatch | Manual JSON edit | Regenerate from job |
| Zero closed tickets | Sprint not started | Skip receipt; document in ops log |
| Partner asks for proof | Only CSV attached | Add CLOSURE_RECEIPT.json |
Verification checklist
- [ ] Deliberate > 25 pp drift blocks publish without note
- [ ] Note ≥ 20 chars clears gate when acknowledged in ticket
- [ ] Receipt SHA-256 in
BUILD_RECEIPT.json - [ ] Block 6 yes/no matches
within_variance_gate - [ ] Closed mix excludes
openandcarved_back_pending
Mini exercise (25 minutes)
- Use Lesson 183 sample CSV (three
hot, onestructural-red). - Draft budget mix 40% Lesson 167, 30% 170, 30% reserved.
- Close tickets: 50% 167, 50% 170 — compute delta.
- Write one-sentence
variance_noteif you force 60% 170. - Export receipt JSON; verify gate behavior.
Continuity
- Lesson 183 — CSV export into feed.
- Lesson 173 — allocator and Block 6 metric.
- Lesson 187 — red-team-sourced closures appear in closed mix via shared tags.
- Next: Lesson 189 — single ZIP year-opening risk letter packet (trend board + mock-audit rollup + bind index).
- Blog: 30-minute weekly indie studio operating review — Block 6 home.
- Resource: Q3 mock audit packet templates — evidence folder layout.
FAQ
Does every sprint need a receipt?
Any sprint that ingested a Lesson 183 export for an active cert window—yes.
Can variance gate be waived?
Only via Lesson 163 carve-out with explicit carve_out_reason referencing receipt ID—not silent override.
What if engineering matched budget but on wrong tickets?
Closed mix still measures lesson IDs; fix ticket tagging, not the receipt math.
Does this replace trend board refresh?
No. Nightly deficiency_recurrence_12w still runs; receipts prove sprint execution, not recurrence analytics.
2026 H2 partners trust spreadsheets when receipts show closed work matched the steering wheel you published in Lesson 173. Sign the closure, pin the SHA-256, and let Block 6 stay a honest yes/no—not a vibe check.