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.

Last reviewed September 17, 2026Fresh

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 parameterTypeRequiredDefault
daysinteger 1 to 365no30
tenant_iduuidnoyour 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
}
StatusBodyCause
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 keySourceReported
audit_trailshield_audit_logtotal entries, breakdowns by action, actor type and result, denied and failure counts
dsarshield_dsar_requeststotal, by status, by type, completion rate, mean resolution in hours
incidentsshield_eventstotal 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_credentialsshield_vc_issuedtotal issued, currently valid, by type, post-quantum signed count and coverage percentage
pii_vaultpii_decryption_logtotal purpose-bound decryptions, by purpose
cross_referencesshield_cross_referencestotal bindings, by match method, post-quantum signed count
cached_proofsshield_cached_proofstotal, currently valid, by proof type, by trust level
entropy_provenanceshield_entropy_pool and five artifact tablesreseeds 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 keySource limit
audit_trail1,000 rows
dsar500 rows
incidents1,000 rows
verifiable_credentials1,000 rows
pii_vault1,000 rows
cross_references1,000 rows
cached_proofs1,000 rows
entropy_provenance1,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_p50 and incidents.decision_ms_p95: the median and 95th percentile of decision_ms over the period, in whole milliseconds, with incidents.decision_ms_samples giving the number of events they were computed from. Both read null below 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.

Related from the blog