Completes the Report Clarity & Executive Briefing milestone and advances Trust at Scale. The report now opens with a decision-maker Executive Briefing, summary visuals speak plain English, the shipped registry is partitioned to what the tool actually assesses (with CI-enforced generated statistics), every check carries an explicit severity rating, large tenants get complete Graph collections via pagination/throttling retry, and EXO 3.8+ installs no longer have to be uninstalled to run an assessment. No breaking API changes.
Added
Invoke-SafeGraphRequest— Graph pagination + throttling retry (#952) — new shared helper inCommon/that follows@odata.nextLinkuntil collections are exhausted (bounded by a page cap that warns instead of truncating silently) and retries 429/503/504 responses with Retry-After-aware exponential backoff. First migrations:Get-EntAppSecurityConfig(replaces five hand-rolled pagination loops), the Stryker risky-apps query, and the Conditional Access policy fetch — tenants beyond one Graph page of apps/service principals/grants/policies now get complete results. Remaining call sites migrate incrementally under #952.- Executive Briefing first screen (#963): the report now opens with a compliance-led briefing built for decision-makers. A verdict card answers "are we compliant?" for the headline framework (readiness label, donut, plain-English coverage sentence with a quick-win projection, framework switcher chips), three stat tiles cover actionable criticals / quick wins / Microsoft Secure Score (demoted from hero), and a "What to do first" list deep-links the top Now-lane actions into the findings table. No CheckIDs, status vocabulary, or unexpanded acronyms appear on this screen. The old critical banner is superseded by the "Needs attention now" tile.
-HeadlineFrameworkparameter (#963): sets which framework(s) headline the Executive Briefing (tab-completes fromcontrols/frameworks/*.json; unknown ids fail fast with the valid list). Defaults to CIS Microsoft 365. The FrameworkQuilt opens on the same headline framework so both sections tell one story.REPORT_DATAgainsheadlineFrameworksandassessedAt.- Generated registry statistics with CI drift gate — public check counts and framework coverage numbers are now generated from
controls/registry.jsonbyscripts/Build-RegistryStats.ps1into marker blocks in README.md,controls/README.md, anddocs/user/COMPLIANCE.md, plus a new fully-generateddocs/reference/COVERAGE.md(per-framework mapping coverage, CISA SCuBA product-pillar coverage, per-collector counts, severity-rating and learn-more completeness). A new "Registry stats in sync" CI quality gate runs the script with-Checkand fails any PR that changes registry data without regenerating the docs — hand-typed check counts can no longer drift from the shipped registry.
Changed
- EXO 3.8.0+ no longer needs to be uninstalled — side-by-side version pinning (#231) — the module helper previously force-uninstalled ALL ExchangeOnlineManagement versions before installing 3.7.1, breaking other tooling that needs newer EXO. Now: if any compatible (< 3.8.0) version is installed alongside a newer one, the gate passes and
Connect-Servicepins the session import to it (Import-Module -RequiredVersion); when no compatible version exists, the repair installs 3.7.1 side-by-side without touching newer versions. The upstream MSAL conflict (msgraph-sdk-powershell#3576) remains unfixed as of EXO 3.10.0, so the < 3.8.0 session requirement itself is unchanged. - All 292 checks now carry explicit severity ratings (#956) — the 86 checks that silently displayed the
Mediumfallback are now rated inrisk-severity.json, seeded from the upstream CheckIDimpactRating.severity(38 High, 42 Medium, 2 Critical, 2 Low, 2 Info) and marked as seeded for future M365-context curation; the curated-vs-seeded rubric is documented incontrols/README.md.-QuickScanselects ~40 more checks than before (the seeded Critical/High additions) — runtime grows accordingly. COVERAGE.md now reports severity completeness at 292 of 292. - Report clarity pass (#962): summary visuals now group Skipped/Unknown/NotApplicable/NotLicensed as one muted "Not assessed" bucket (donut, framework bars, domain cards, conditional KPI tile); the findings table, filter chips, and appendix keep the full nine-status vocabulary. CheckID column ships hidden by default (re-enable via Columns). New "How to read this table" legend with one-sentence tooltips on every status badge. Number formats standardized to "N of M". Jargon sweep: PIM/SPF/DKIM/DMARC expanded on first use, plain-language scoring blurbs, "Targeted" eyebrow renamed "Critical exposure".
- Registry partitioned to M365 collector scope —
controls/registry.jsonpreviously carried the entire upstream CheckID registry, including 814 Windows-endpoint (WIN-*) and Azure-subscription (AZ-*) checks that no collector in this module can emit. The registry is now filtered to the 15 M365 collector families declared in the newcontrols/sync-scope.json(292 checks, including the 5 local extensions), shrinking the shipped registry from ~7.0 MB to ~3.0 MB and making every check count and framework statistic reflect what the tool actually assesses. Thesync-checkidworkflow applies the same partition on every upstream sync; out-of-scope content remains available in the upstream CheckID release.Import-ControlRegistryenforces the scope at load time as defense-in-depth and warns if an unpartitioned registry is detected. - Registry entries now expose
severityRated—Import-ControlRegistrymarks whether a check'sriskSeveritycame from an explicit rating inrisk-severity.json($true) or is theMediumfallback ($false), so downstream consumers can distinguish rated findings from defaulted ones. Display behavior is unchanged; 206 of 292 checks currently carry explicit ratings.
Fixed
- NaN counts for not-assessed statuses in framework math (#962):
buildFrameworkDataincremented count keys it never initialised for the four exotic statuses, producingNaNand a silent donut gap. Counting now routes through onesummaryBucket()helper. - Mismatched denominators in two KPI hints (#962): the Fails KPI hint said "of all checks" and the SharePoint pass-rate hint counted unscored checks, while their bars used the scored-check denominator. Both now say "of N scored checks".
- Framework group keys rendered as raw codes in the framework breakdown (#948) — several framework group maps were missing labels for groups the registry maps checks into, so those groups displayed the bare section number/prefix as the group name:
cis-m365-v6.jsonsections4(Intune) and9(Fabric / Power BI),cisa-scuba.jsonserviceMS.INTUNE,hipaa.jsonthe ten Breach Notification + Privacy Rule sections added by the CheckID v3.4.0 sync (#912), andiso-27002.jsongroups9/10(ISO 27001 management-clause ids that leak in via the upstream 27001/27002 mapping conflation, #871 — labels mirroriso-27001.jsonand can be dropped when upstream diverges the mappings). A new regression test cross-checks every group key derivable from registry controlIds against each framework's group map so a registry sync can no longer reintroduce unlabeled groups silently. - Stale check counts in docs —
docs/user/COMPLIANCE.mdcited three conflicting registry sizes (294, 1106, and 270); all now state the real M365-scoped count (292). The Metadata-Consistency test that guards the COMPLIANCE.md count pointed at a file location retired in the #906 docs consolidation and silently skipped; it now checksdocs/user/COMPLIANCE.mdand actually enforces the count. The registry-integrity suite gained a test that fails if an unpartitioned registry is ever committed, and the valid-collector test now derives its allowlist fromcontrols/sync-scope.jsoninstead of a hardcoded copy.