SOC-2 Quarterly Attestation Runbook
Owner: operator (compliance program owner)
System scope: Raptor audit system (customer_audit_events, HMAC chain, archiver)
Card: #1496
Phase: Audit Phase 3 — usable after SC-A11 + SC-A13 land
Last reviewed: 2026-05-17 UTC
Related:
- SC-A11 — HMAC integrity check job (jobs/audit_integrity_check.py)
- SC-A13 — archiver manifest (audit_archival_runs)
- RV-13 — compliance role + raxx-compliance-auditors group
- SC-A15 — departing-employee deprovisioning runbook
1. Scope
This runbook covers the SOC-2 controls attested to by Raxx's audit system in the pre-customer phase. Controls outside this scope (vendor management, change management, incident response) are out of scope until a formal SOC-2 engagement begins.
| Control | AICPA ref | What Raxx attests |
|---|---|---|
| Logical access — least privilege | CC6.1 | raptor-audit-compliance role assigned only to raxx-compliance-auditors group; no direct user grants |
| Logical access — access review | CC6.2 | raxx-compliance-auditors group membership reviewed quarterly; provisioned empty per OQ-3 decision |
| Logical access — termination | CC6.3 | Departing-employee deprovisioning runbook (SC-A15) executed within 24 h of notice |
| Change management — audit trail integrity | CC8.1 | HMAC-SHA-256 + AWS KMS chain verified monthly; no gaps in audit_integrity_log |
| Data retention | A1.2 | Archiver manifest confirms retention-policy exports within 90 days; 7-year cold storage SLA in effect |
| Monitoring — log completeness | CC7.2 | pg_audit external sink shows DML events for last 90 days with no unexplained gaps |
| Access change control | CC6.7 | rbac_grants_audit shows no out-of-window grants to raptor-audit-compliance or raptor-audit-admin |
2. Quarterly schedule
Attestation must be completed within ±5 days of each quarter-end. Target window opens on the last business day of the quarter and must close no later than five calendar days after the UTC dates below.
| Quarter | Target date (UTC) | Open window | Hard close |
|---|---|---|---|
| Q1 | 2026-01-01 10:00 UTC | 2025-12-27 | 2026-01-06 |
| Q2 | 2026-04-01 10:00 UTC | 2026-03-27 | 2026-04-06 |
| Q3 | 2026-07-01 10:00 UTC | 2026-06-26 | 2026-07-06 |
| Q4 | 2026-10-01 10:00 UTC | 2026-09-26 | 2026-10-06 |
The operator attestation commit (Step 8) must be timestamped within the hard close window. The commit timestamp is the evidence of timely completion.
3. Per-control evidence checklist
Step 1 — Compliance role assignment (CC6.1 / RV-13)
Confirm raptor-audit-compliance is assigned only to the
raxx-compliance-auditors group and carries no direct user assignments.
Where to collect:
Run from a Postgres session authenticated as the owner credential (migration
credential from Heroku; use heroku pg:psql --app raxx-api-prod):
-- Show all group members for raxx-compliance-auditors
SELECT u.email, gm.granted_at, gm.granted_by
FROM rbac_group_members gm
JOIN users u ON u.id = gm.user_id
JOIN rbac_groups g ON g.id = gm.group_id
WHERE g.name = 'raxx-compliance-auditors';
-- Confirm no direct user grants for raptor-audit-compliance
SELECT u.email, rg.granted_at, rg.granted_by
FROM rbac_grants rg
JOIN users u ON u.id = rg.user_id
JOIN rbac_roles r ON r.id = rg.role_id
WHERE r.name = 'raptor-audit-compliance'
AND rg.group_id IS NULL;
Expected result: Group query returns zero rows (group is provisioned empty per OQ-3 decision until an external auditor is engaged). Direct-grant query returns zero rows.
Format: Screenshot of psql output, or copy-paste into evidence file.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / CC6.1-role-assignment.png
Step 2 — Auditor group membership review (CC6.2 / RV-13)
Confirm raxx-compliance-auditors contains only authorized auditors (or is
empty). Cross-reference against the most recent authorization memo in
docs/ops/attestation-log/.
Where to collect: Same psql query as Step 1 (group member query). If the group is non-empty, verify each email against the current authorization memo.
Format: Screenshot or text export.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / CC6.2-group-membership.txt
Step 3 — HMAC chain monthly verification (CC8.1 / SC-A11)
Run the monthly chain verification and confirm clean output:
# On the Raptor dyno or a one-off Heroku run:
heroku run --app raxx-api-prod \
python jobs/audit_integrity_check.py --mode monthly
Confirm the job exits 0 and writes a PASS record to audit_integrity_log
with no chain_error or kms_error fields set.
Verify in Postgres:
SELECT run_id, mode, started_at, completed_at, result, rows_verified, errors
FROM audit_integrity_log
WHERE mode = 'monthly'
ORDER BY started_at DESC
LIMIT 3;
Expected result: Most recent row shows result = 'pass', errors = 0,
completed_at within the current quarter window.
Format: Terminal output screenshot + CSV export of the query above.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / CC8.1-hmac-chain-monthly.png
Step 4 — Archiver manifest completeness (A1.2 / SC-A13)
Confirm the archiver manifest in audit_archival_runs shows all
retention-policy exports completed within the last 90 days.
SELECT run_id, started_at, completed_at, rows_archived, destination,
retention_policy_label, status
FROM audit_archival_runs
WHERE started_at > NOW() - INTERVAL '90 days'
ORDER BY started_at DESC;
Expected result: At least one completed run per calendar month in the
window. All rows show status = 'complete'. No status = 'failed' rows.
Format: CSV export (\copy to local file from psql).
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / A1.2-archiver-manifest.csv
Step 5 — 7-year retention bounds (A1.2 / SC-A13)
Verify retention policy bounds:
-- Oldest unarchived row in hot table (must be < 7 years old)
SELECT MIN(occurred_at) AS oldest_hot_row
FROM customer_audit_events;
-- Most recent S3 Glacier archive job (cold storage)
SELECT destination, rows_archived, completed_at
FROM audit_archival_runs
WHERE destination LIKE 's3://raxx-audit-glacier%'
ORDER BY completed_at DESC
LIMIT 1;
Confirm: oldest_hot_row is within the 7-year policy window. Glacier
archive job completed within the last 90 days.
Format: Screenshot of psql output.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / A1.2-retention-bounds.png
Step 6 — pg_audit external log sink (CC7.2)
Confirm DML events are present in the external pg_audit sink for the last 90 days with no unexplained gaps.
Log drain is configured on raxx-api-prod and ships to the centralized log
sink. Check via Heroku log drain or the configured APM (Sentry) log intake:
# Pull a sample of recent pg_audit lines from Heroku log drain (last 24 h)
heroku drains --app raxx-api-prod
# Confirm drain is active, then pull from your log aggregator for
# DML class events on customer_audit_events for the past 90 days.
Expected result: DML events present for every day in the 90-day window.
Any gap exceeding 24 h that is not accounted for by a documented maintenance
window is a finding — file a severity:high area:security issue before
signing the attestation.
Format: Exported log sample (CSV or text), date-range annotated.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / CC7.2-pgaudit-sample.txt
Step 7 — Out-of-window RBAC grant review (CC6.7)
Confirm no raptor-audit-compliance or raptor-audit-admin grants
occurred outside of documented change windows in the last quarter.
SELECT u.email, r.name AS role, rg.granted_at, rg.granted_by,
rg.change_window_id
FROM rbac_grants_audit rg
JOIN users u ON u.id = rg.user_id
JOIN rbac_roles r ON r.id = rg.role_id
WHERE r.name IN ('raptor-audit-compliance', 'raptor-audit-admin')
AND rg.granted_at > NOW() - INTERVAL '90 days';
Cross-reference any rows against the change management log
(docs/ops/runbooks/deploy-freeze.md). Any grant with a null or
unrecognized change_window_id is a finding — do not sign the attestation
until resolved.
Format: CSV export of the query.
Storage: Private Google Drive — Raxx / Compliance / YYYY-QN / CC6.7-rbac-grants-audit.csv
Step 8 — Operator attestation sign-off
Create a signed attestation commit in this repository:
- Copy
docs/ops/attestation-log/YYYY-QN.mdtemplate (see2026-Q2.md). - Fill in the date, quarter, and pass/fail result for each of the seven steps.
- Note any open findings with linked GitHub issue numbers.
- Commit to
main(via PR if a review is desired; direct commit is acceptable for operator-only sign-off):
git add docs/ops/attestation-log/YYYY-QN.md
git commit -m "ops(compliance): Q<N> YYYY SOC-2 attestation sign-off"
git push origin main
The commit SHA and UTC timestamp serve as the dated, signed statement of operator confirmation. The commit must fall within the hard close window defined in §2.
Storage: Git history is the authoritative record. A copy of the file is
also stored in Private Google Drive — Raxx / Compliance / YYYY-QN / attestation.md
4. Operator action checklist (sequential)
Run these in order. Do not skip a step if the previous step produced a finding — document the finding and decide whether to proceed with a qualified attestation or delay sign-off.
- [ ] Pre-flight: confirm SC-A11 (
audit_integrity_check.py) and SC-A13 (archiver) are operational on staging before running against prod. - [ ] Step 1: compliance role assignment — zero rows on both queries.
- [ ] Step 2: auditor group membership — empty or all members authorized.
- [ ] Step 3: HMAC chain monthly run — job exits 0,
result = 'pass'. - [ ] Step 4: archiver manifest — all runs
status = 'complete', no gaps. - [ ] Step 5: retention bounds — oldest hot row within 7 years, Glacier job completed within 90 days.
- [ ] Step 6: pg_audit sink — no unexplained 24+ h gaps in DML events.
- [ ] Step 7: RBAC grant review — no out-of-window grants for audit roles.
- [ ] File a
severity:high area:securityissue for every finding before committing the attestation. - [ ] Step 8: commit attestation sign-off to
docs/ops/attestation-log/YYYY-QN.md. - [ ] Upload evidence bundle to Private Google Drive
Raxx / Compliance / YYYY-QN /.
5. Document retention
| Evidence type | Retention period | Notes |
|---|---|---|
Attestation log file (YYYY-QN.md) |
Minimum 7 years | Retained in git history + Google Drive |
| RBAC grant audit export | 7 years | Supports SOC-2 Type II period-of-coverage requirement |
| HMAC chain verification log | 7 years | Same as above |
| Archiver manifest export | 7 years | Cold storage SLA evidence |
| pg_audit log samples | 7 years | Log drain retention may be shorter; operator must archive externally |
| Role + group membership screenshots | 1 year minimum; 7 years recommended | Extend to 7 years once SOC-2 Type II engagement begins |
All evidence stored in Google Drive must be in the private Raxx / Compliance /
folder with access restricted to operator + engaged auditors only.
6. Future automation candidates
These steps are manual in Phase 1. Post-launch automation targets:
| Step | Candidate automation | Notes |
|---|---|---|
| Step 1–2 | Script to query RBAC tables and emit pass/fail JSON | Add to scripts/compliance/ |
| Step 3 | Nightly HMAC job already runs automatically; add quarterly summary email | SC-A11 extension |
| Step 4–5 | Archiver already produces audit_archival_runs rows; add a --report flag to emit a formatted evidence file |
SC-A13 extension |
| Step 6 | Add pg_audit gap-detection query to the nightly integrity job | SC-A11 extension |
| Step 7 | Add a scheduled query that flags out-of-window grants and fires to #raxx-ops-alert-sev2 |
Queue RBAC service extension |
| Step 8 | Auto-draft attestation file from script output; operator reviews and commits | Phase 2 automation card |
Reference documents
docs/architecture/customer-audit-unified/design.md
docs/architecture/customer-audit-unified/api-contract.md
docs/ops/runbooks/raptor-postgres-roles.md
docs/ops/attestation-log/2026-Q2.md
Privacy policy (evidence of user-facing data commitments):
docs/architecture/privacy-policy-v1.md