Lesson 182: Cert Rehearsal Attendance Receipt and mock_audit_log Bootstrap from ICS Completion (2026)

Direct answer: Lesson 181 put ICS holds on the calendar. Lesson 182 closes the loop: when a hold completes, you write an attendance receipt (cert_rehearsal_attendance) keyed to the ICS UID, then bootstrap a draft mock_audit_log row only after receipt validation—so Lesson 173 recurrence histograms count real tabletops, not phantom invites.

Lesson hero for cert rehearsal attendance receipt and mock audit log bootstrap

Why this matters now (Q4 2026 partner readbacks)

Q4 2026 year-end governance readbacks shifted from “did you schedule a mock audit?” to “prove the tabletop ran”:

  • Empty mock_audit_log with full calendars is a yellow on partner risk letters.
  • Lesson 173 deficiency_recurrence_12w treats undated logs as noise—calendar-only teams look “stable” while never rehearsing.
  • Lesson 174 signer fatigue heat-maps assume ack traffic during real cert windows tied to completed rehearsals.
  • Lesson 180 red-team runs must attach to a mock_audit_log_id that exists—not a Slack thread.

Attendance receipts are the mechanical bridge between operations (calendar) and analytics (trend board).

Lesson objectives

You will implement:

  • cert_rehearsal_attendance table bound to cert_rehearsal_event.ics_uid
  • forbid_mock_audit_log_without_attendance database trigger
  • bootstrap_mock_audit_log_from_attendance job (draft → scored)
  • Panel roster validation against Lesson 172 four roles
  • ATTENDANCE_RECEIPT.json export under release-evidence/05-operations/
  • Publish-gate extension: mock_audit_log_orphan block reason

Prerequisites

  • Lesson 181 — ICS export, cert_window_calendar, cert_rehearsal_event
  • Lesson 172 — scoring rubric, panel roles, 90-minute duration
  • Lesson 173 — consumes dated mock_audit_log rows
  • Lesson 171 — publish pipeline; attendance does not bypass tuple drift gates

Schema: cert_rehearsal_attendance

CREATE TABLE cert_rehearsal_attendance (
  attendance_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  cert_window_id         TEXT NOT NULL REFERENCES cert_window_calendar(cert_window_id),
  ics_uid                TEXT NOT NULL UNIQUE,
  rehearsal_phase        TEXT NOT NULL CHECK (rehearsal_phase IN ('t_minus_14', 't_minus_3')),
  scheduled_start_utc    TIMESTAMPTZ NOT NULL,
  actual_start_utc       TIMESTAMPTZ,
  actual_end_utc         TIMESTAMPTZ,
  duration_minutes       INT GENERATED ALWAYS AS (
    EXTRACT(EPOCH FROM (actual_end_utc - actual_start_utc)) / 60
  ) STORED,
  scribe_signer_id       TEXT NOT NULL,
  panel_present_json     JSONB NOT NULL,
  attendance_status      TEXT NOT NULL DEFAULT 'scheduled'
    CHECK (attendance_status IN ('scheduled','completed','cancelled','no_show')),
  receipt_sha256         CHAR(64),
  created_at_utc         TIMESTAMPTZ NOT NULL DEFAULT now()
);

Rules:

  • ics_uid must exist in cert_rehearsal_event (Lesson 181).
  • panel_present_json must list all four Lesson 172 roles with non-empty signer_id.
  • duration_minutes must be ≥ 75 and ≤ 120 for completed (90-minute tabletop slack).

Trigger: forbid orphan mock_audit_log

CREATE OR REPLACE FUNCTION forbid_mock_audit_log_without_attendance()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.attendance_id IS NULL THEN
    RAISE EXCEPTION 'mock_audit_log requires attendance_id';
  END IF;
  IF NOT EXISTS (
    SELECT 1 FROM cert_rehearsal_attendance a
    WHERE a.attendance_id = NEW.attendance_id
      AND a.attendance_status = 'completed'
  ) THEN
    RAISE EXCEPTION 'attendance must be completed before mock_audit_log insert';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_mock_audit_log_attendance
  BEFORE INSERT ON mock_audit_log
  FOR EACH ROW EXECUTE FUNCTION forbid_mock_audit_log_without_attendance();

This enforces the cultural rule: no scoring row without a completed rehearsal receipt.

Bootstrap job sketch

Run after scribe closes the tabletop (within 15 minutes):

def bootstrap_mock_audit_log(attendance_id: str) -> str:
    att = load_attendance(attendance_id)
    assert att["attendance_status"] == "completed"
    assert att["duration_minutes"] >= 75
    rubric = load_json("governance/mock_audit_rubric_v1.json")
    log_id = insert_mock_audit_log(
        attendance_id=attendance_id,
        cert_window_id=att["cert_window_id"],
        rubric_version=rubric["rubric_version"],
        status="draft",
        pass_bit=None,  # computed after scoring
    )
    write_receipt(att, log_id)
    return log_id

Draft vs scored: Panel scores in Lesson 172 UI or spreadsheet import flips status from draftscored and sets computed pass bit—never manual toggle.

ATTENDANCE_RECEIPT.json

Commit per completed rehearsal:

{
  "attendance_id": "8f2c-…",
  "cert_window_id": "q1_2027_meta_holiday",
  "ics_uid": "[email protected]",
  "rehearsal_phase": "t_minus_14",
  "scheduled_start_utc": "2027-01-08T14:00:00Z",
  "actual_start_utc": "2027-01-08T14:03:00Z",
  "actual_end_utc": "2027-01-08T15:28:00Z",
  "panel_present": [
    {"role": "scribe", "signer_id": "eng.alice"},
    {"role": "leadership_voice", "signer_id": "ceo.bob"},
    {"role": "partner_voice", "signer_id": "partner.liaison"},
    {"role": "engineering_voice", "signer_id": "eng.carol"}
  ],
  "mock_audit_log_id": "log-…",
  "receipt_sha256": "…"
}

Store under release-evidence/05-operations/rehearsal-attendance/.

Six-step completion procedure

Step Owner Action SLA
1 Scribe Mark ICS event accepted; record actual_start_utc T+0
2 Scribe Verify four panel roles present T+5 min
3 Scribe Insert cert_rehearsal_attendancecompleted T+10 min
4 Engineering Run bootstrap job → mock_audit_log draft T+15 min
5 Panel Score tabletop → scored + computed pass T+24 h
6 Governance owner Export receipt JSON + SHA-256 to evidence folder T+48 h

Publish-gate extension

Add to Lesson 171 block_reason allow-list:

  • mock_audit_log_orphan — publish blocked while any mock_audit_log row lacks attendance_id or attendance not completed.

Integration with Lesson 173 trend board

After bootstrap + scoring:

  1. Nightly refresh deficiency_recurrence_12w includes only mock_audit_log.status = 'scored'.
  2. cert_window_id on log rows powers cross-window histograms.
  3. Friday Block 6 compares sprint hardening budget to closed deficiencies from scored logs only.

Common mistakes

  • Inserting mock_audit_log before attendance completed.
  • Same human listed for two panel roles (Lesson 172 allows distinct voices—receipt should too).
  • Using calendar “maybe” responses as attendance proof.
  • Skipping receipt_sha256 on export (partner cannot reproduce).
  • Marking no_show but still inserting deficiencies “from memory.”
  • Bootstrap job creating duplicate logs for same ics_uid (enforce UNIQUE on attendance → one log).

Troubleshooting

Symptom Likely cause Fix
Trigger rejects insert Attendance still scheduled Complete step 3 first
Duration check fails End time typo Reconcile with scribe notes
Missing role in JSON Panel no-show Reschedule; do not fake receipt
Duplicate ics_uid Re-exported ICS with new UID Lesson 181 UID stability rule
Trend board empty Logs stuck in draft Run scoring step 5

Verification checklist

  • [ ] Completed rehearsal has cert_rehearsal_attendance row
  • [ ] mock_audit_log.attendance_id FK populated
  • [ ] Trigger blocks insert without completed attendance
  • [ ] ATTENDANCE_RECEIPT.json in evidence folder with SHA-256
  • [ ] Lesson 173 view shows new window after scored log
  • [ ] Publish gate fires on deliberate orphan log test

Mini exercise (25 minutes)

  1. Pick one Lesson 181 ICS UID from your calendar export.
  2. Write a fake-but-valid attendance JSON (four roles, 85-minute duration).
  3. Draft SQL insert statements for attendance + draft log.
  4. List one block_reason that still applies from Lesson 171.

Continuity

  • Lesson 181 — ICS source; do not change UID scheme here.
  • Lesson 172 — scoring rules unchanged; this lesson only gates existence of log rows.
  • Lesson 173 — primary consumer of scored logs.
  • Lesson 180 — attach red-team packet IDs to mock_audit_log_id from this bootstrap.
  • Next: Lesson 183 — export deficiencies to sprint allocator feed.
  • Blog: Q3 2026 mock audit tabletop — panel format reference.

FAQ

Can we backfill 2026 Q3 logs without receipts?
One-time migration with attendance_status = 'completed' and archived evidence only—do not make it the default path.

What if only three panelists attend?
Mark cancelled, reschedule. Partial panels fail Lesson 172 muscle-memory goal.

Does Google Calendar auto-write attendance?
No. Scribe (or delegate) must insert SQL/JSON within 15 minutes—calendar is schedule, not proof.

ICS declined—still bootstrap?
No. no_show blocks bootstrap by design.


Scheduled rehearsals are intentions. Attendance receipts plus gated mock_audit_log bootstrap are proof—and proof is what 2026 partner readbacks count.