CISO compliance report
One request that aggregates audit, data-subject, incident, credential, vault, binding, proof and entropy counts for a period, with explicit truncation flags.
Overview
shield-compliance-report aggregates one organization's audit, data-subject, incident, credential, vault, binding, proof and entropy figures for a period you choose, in one request. It is the data behind the CISO Compliance Report panel in the dashboard.
Who can use it. A tenant admin with a dashboard session. No plan gate. The function is console-only: it is not in the worker allowlist, so there is no https://api.passkeybridge.io/v1/shield-compliance-report. The dashboard calls it with supabase.functions.invoke.
What it does not do. It does not evaluate controls or produce a compliance opinion. It counts rows in your own tables for the period and reports the counts, plus explicit truncation flags when a section hit its query limit. PasskeyBridge's own SOC 2 position is readiness: evidence collected and under review.
Every generation writes a compliance_report.generate audit entry with the period length, so the report itself is auditable.
Request and response
GET only. Any other method answers 405 Method not allowed. Use GET.
const { data, error } = await supabase.functions.invoke(
"shield-compliance-report?days=90",
{ method: "GET" },
);{ method: "GET" } is required. The Supabase functions client sends POST unless you say otherwise, and this function answers 405 to anything but GET.
| Query parameter | Type | Required | Default |
|---|---|---|---|
days | integer 1 to 365 | no | 30 |
tenant_id | uuid | no | your oldest membership |
The response opens with the period metadata, and carries truncated plus truncation_note only when a section hit its limit.
{
"report_version": "1.2.0",
"generated_at": "2026-09-16T12:00:00.000Z",
"period": { "start": "2026-06-18T12:00:00.000Z", "end": "2026-09-16T12:00:00.000Z", "days": 90 },
"audit_trail": { "total_entries": 842, "by_action": {}, "by_actor_type": {}, "by_result": {}, "denied_count": 3, "failure_count": 11 },
"dsar": { "total_requests": 4, "by_status": {}, "by_type": {}, "completion_rate": 75.0, "mean_resolution_hours": 6.2 },
"incidents": { "total_events": 1482, "by_type": {}, "by_result": {}, "high_risk_count": 38, "critical_risk_count": 12, "mean_response_ms": 164, "decision_ms_samples": 1482, "decision_ms_p50": 118, "decision_ms_p95": 267 },
"verifiable_credentials": { "total_issued": 96, "currently_valid": 91, "by_type": {}, "pqc_signed_count": 96, "pqc_coverage_pct": 100.0 },
"pii_vault": { "total_decryptions": 7, "by_purpose": {}, "architecture": "Zero-PII (hashed identifiers, purpose-bound decryption)" },
"cross_references": { "total": 12, "by_method": {}, "pqc_signed_count": 12 },
"cached_proofs": { "total": 33, "currently_valid": 30, "by_type": {}, "by_trust_level": {} },
"entropy_provenance": { "reseeds_attributed": 18, "reseeds_by_provider": {}, "health_tests": {}, "receipt_eligible": {}, "receipt_endpoint": "GET https://api.passkeybridge.io/v1/shield-entropy-receipt/{tenant_id}?artifact_type=<type>&artifact_id=<id>" },
"latency_ms": 412
}| Status | Body | Cause |
|---|---|---|
| 400 | {"error":"Invalid 'days' parameter: must be 1-365"} | days outside the range or not a number |
| 401 | {"error":"Missing authorization header"} or {"error":"Invalid auth token"} | no session, or a session that did not resolve |
| 403 | {"error":"Admin role required for compliance reports"} | member without the admin role |
| 404 | {"error":"No tenant found"} | the caller has no membership, or none matching tenant_id |
| 405 | {"error":"Method not allowed. Use GET."} | any method other than GET |
| 502 | {"error":"Data fetch failed"} | one of the underlying queries errored; detail stays in the server log |
Report sections
Eight sections, each aggregated from one source and each limited to the period.
| Section key | Source | Reported |
|---|---|---|
audit_trail | shield_audit_log | total entries, breakdowns by action, actor type and result, denied and failure counts |
dsar | shield_dsar_requests | total, by status, by type, completion rate, mean resolution in hours |
incidents | shield_events | total events, by type, by result, high and critical risk counts, mean response_ms, and the decision_ms sample count with its p50 and p95 |
verifiable_credentials | shield_vc_issued | total issued, currently valid, by type, post-quantum signed count and coverage percentage |
pii_vault | pii_decryption_log | total purpose-bound decryptions, by purpose |
cross_references | shield_cross_references | total bindings, by match method, post-quantum signed count |
cached_proofs | shield_cached_proofs | total, currently valid, by proof type, by trust level |
entropy_provenance | shield_entropy_pool and five artifact tables | reseeds attributed to the tenant, by provider, health-test outcomes, receipt-eligible artifact counts, the receipt endpoint |
Risk banding in incidents is its own: critical_risk_count counts events with risk_score at or above 0.8 and high_risk_count those from 0.5 up to 0.8. The metrics endpoint bands differently (above 0.8 and above 0.95), so the two surfaces will disagree on the same events by design.
receipt_eligible counts, per artifact type (webhook_delivery, security_event_token, credential, cached_proof, cross_reference), how many rows in the period exist and how many name the entropy seed behind their signature. Those counts are exact database counts, not sampled.
Truncation flags
Each section reads its source with a row limit. When a section comes back at its limit, the report adds a flag under truncated and the counts for that section are lower bounds.
truncated key | Source limit |
|---|---|
audit_trail | 1,000 rows |
dsar | 500 rows |
incidents | 1,000 rows |
verifiable_credentials | 1,000 rows |
pii_vault | 1,000 rows |
cross_references | 1,000 rows |
cached_proofs | 1,000 rows |
entropy_provenance | 1,000 rows |
{
"truncated": { "audit_trail": true, "incidents": true },
"truncation_note": "Some sections hit query limits. Actual counts may be higher than reported."
}A busy organization will truncate a 365-day report. Narrow days and run several reports rather than reading a truncated total as a period total. The receipt_eligible counts inside entropy_provenance are unaffected, because they are counted in the database rather than by reading rows.
Computed metrics
Four figures are derived rather than counted, and each is null when the period holds nothing to compute it from.
dsar.completion_rate: completed requests over total requests, as a percentage to one decimal place.dsar.mean_resolution_hours: mean time from request creation to completion, over completed requests only, to one decimal place.verifiable_credentials.pqc_coverage_pct: issued credentials carrying a post-quantum algorithm, over all issued, to one decimal place.incidents.decision_ms_p50andincidents.decision_ms_p95: the median and 95th percentile ofdecision_msover the period, in whole milliseconds, withincidents.decision_ms_samplesgiving the number of events they were computed from. Both readnullbelow 20 samples, because a percentile drawn from a handful of events is not a percentile.
These replaced a sub_50ms_rate field on 2026-09-17. That field counted events under a fixed fifty millisecond cut-off, computed from response_ms, and the dashboard rendered it under a service-level heading coloured green above 90 percent. PasskeyBridge commits to no latency level: section 8 of the Terms targets 99.9 percent uptime and is silent on latency. response_ms also measures the whole request, including time awaited on outbound playbook actions such as Slack, your callback endpoint and email, so it was scoring your own webhook against the platform's decision budget. mean_response_ms is still reported, as end-to-end context rather than as a target. For the full decision-path distribution with histograms, use the latency action described in Observability.
Generating the report
Dashboard > Overview, then open the collapsed Analytics section. The first card is CISO Compliance Report: a period selector offering Last 7, 30, 90, 180 and 365 days, and a Generate Report button.
Once generated, the panel shows an executive summary of four metric cards (Audit Entries with the denied count, DSAR Completion with the request count, Critical Incidents with the high-risk count, PQC Coverage with the number of credentials issued), then breakdown tables per section, and an entropy provenance block with a receipt-eligible artifact table.
The Generate Report button does not work today. The panel calls the function without setting the method, so the request arrives as POST and the function answers 405 Method not allowed. Use GET.; the panel then shows a generic failure toast. Until that one-line fix ships, generate the report with an explicit GET as shown above. The rest of this section describes the panel as it renders once it has a report.
Three controls sit above the report: CSV exports every section as a flat file with formula-injection protection, Print / PDF opens a sanitized print view, and Close clears it. There is no raw JSON export and no auto-refresh; press Generate Report again for a fresh period.
Confirm it worked. A compliance_report.generate entry appears in Dashboard > Audit log with resource_type compliance_report and the period length in its metadata.