manifests/assessments.psd1
|
# # Azure Scout — assessment module registry # # Every assessment declares: # Description human summary # Category the Scout -Category it scopes discovery to ('*' = all) # Collect collect categories to gather # Ingest third-party collectors to fold into collect.json # Rules rule-file glob patterns (caf.<domain> / waf.<domain>) # Frameworks the CAF areas / WAF pillars this assessment maps to # Tags classification tags # Benchmark optional benchmark reference # Reporters default output renderers # # Adding an assessment = adding an entry here plus a rule file. No core change. # See docs/design/assessment-registry.md for the full catalogue (AB#5057). # @{ # ---- cross-category roll-ups ---- LandingZone = @{ Description = 'CAF/WAF landing zone audit (all areas)' Category = '*' # Rules = caf.*, waf.* pulls in every rule file (all 8 CAF areas + all 5 WAF # pillars, including the per-domain rule files from Epic AB#5056), so Collect # must gather every category too, not the 5-category subset this used to list # — now that -Categories actually filters which ARG queries Invoke-Collect # runs, an incomplete list here would silently starve Storage/Databases/Web/ # Containers/Analytics/AI/Integration/Hybrid/IoT/Compute/Cost rules of data. Collect = @('*') Ingest = @('Governance', 'AdvisorScores') # 'xr.*' pulls in the cross-resource rules (AB#6835). A landing-zone audit that could not # say which VMs have no backup was answering a narrower question than its name claims. Rules = @('caf.*', 'waf.*', 'xr.*') Frameworks = @('CAF: all 8 design areas', 'WAF: all 5 pillars', 'XR: Cross-resource posture') Tags = @('caf', 'waf', 'landing-zone', 'cross-resource') Benchmark = 'alz-reference.json' Reporters = @('PowerBi', 'Html', 'Pptx', 'React') } # 'Estate' (full digital-estate inventory, Rules = @()) was removed entirely by AB#6845/#6795. # It scored nothing -- a menu entry that runs and returns nothing reads as "no findings", the # same false negative the provably-broken collectors were retired for (AB#6763 already hid it # from the wizard on exactly that evidence). Inventory is a different product from an # assessment: run it with `Invoke-AzureScout` (no `-Assessment`), which every code path in # this repo already treats as the non-assessment mode. Nothing here replaces that capability; # this only removes the assessment-registry entry that duplicated and mis-scoped it. # ---- per-category assessments (Epic AB#5056) ---- # # The `Assess: ` prefix is AB#6762, and it is a stopgap with a known end date. # # These fifteen entries carried the same names as Scout's fifteen INVENTORY categories -- # Compute, Storage, Networking and so on. One filters what is collected; the other filters # what is scored. Same words, different meaning, and once the wizard menu was fixed to show # the registry at all (AB#6754) they would have appeared side by side in one list. # # Renaming treats the symptom. The end state is retirement: when Release 3 splits LandingZone # into per-WAF-pillar and per-CAF-design-area assessments, an operator wanting Compute # findings picks the pillars, not a category filter over the same rule set. The prefix buys # a legible menu until then. # # Legacy names still work. Resolve-ScoutAssessmentName maps `Compute` to `Assess: Compute` # and warns, so scripted `-Assessment Compute` callers are not broken by a cosmetic change. 'Assess: Management' = @{ Description = 'Governance, policy, cost, backup, automation, update manager' Category = 'Management'; Collect = @('Management'); Ingest = @('Governance', 'AdvisorScores') Rules = @('caf.governance', 'caf.management', 'caf.billing'); Frameworks = @('CAF: Governance', 'CAF: Management', 'CAF: Billing', 'WAF: Operational', 'WAF: Cost') Tags = @('caf', 'governance', 'management'); Reporters = @('Html', 'Excel') } 'Assess: Monitor' = @{ Description = 'Monitoring, alerting, diagnostics coverage' Category = 'Monitor'; Collect = @('Monitor'); Ingest = @() Rules = @('caf.management', 'waf.operational'); Frameworks = @('CAF: Management & monitoring', 'WAF: Operational excellence') Tags = @('waf', 'monitor'); Reporters = @('Html', 'Excel') } 'Assess: Networking' = @{ Description = 'Network topology, firewall, DDoS, exposure, private link' Category = 'Networking'; Collect = @('Networking'); Ingest = @() Rules = @('caf.network'); Frameworks = @('CAF: Network topology & connectivity', 'WAF: Security') Tags = @('caf', 'networking'); Reporters = @('Html', 'Excel') } 'Assess: Identity' = @{ Description = 'Identity & access — PIM, Conditional Access, RBAC' Category = 'Identity'; Collect = @('Identity', 'Security'); Ingest = @('Governance') Rules = @('caf.identity'); Frameworks = @('CAF: Identity & access management', 'WAF: Security') Tags = @('caf', 'identity'); Reporters = @('Html', 'Excel') } 'Assess: Security' = @{ Description = 'Defender, Key Vault, secure score, exposure' Category = 'Security'; Collect = @('Security'); Ingest = @('AdvisorScores') Rules = @('caf.security', 'waf.security'); Frameworks = @('CAF: Security', 'WAF: Security') Tags = @('caf', 'waf', 'security'); Reporters = @('Html', 'Excel') } 'Assess: Compute' = @{ Description = 'VM resilience, zones, backup, right-size, orphans' Category = 'Compute'; Collect = @('Compute'); Ingest = @('AdvisorScores') Rules = @('waf.reliability', 'waf.cost', 'waf.performance'); Frameworks = @('WAF: Reliability', 'WAF: Cost', 'WAF: Performance efficiency') Tags = @('waf', 'compute'); Reporters = @('Html', 'Excel') } 'Assess: Storage' = @{ Description = 'Storage public access, TLS, encryption, redundancy' Category = 'Storage'; Collect = @('Storage'); Ingest = @() # 'waf.storage' was removed here, not replaced: waf.storage.yaml was deleted in the # AB#6746 restructure and its five rules redistributed into the real pillar files # (waf.reliability / waf.security / waf.cost), which are their own assessments. The # dangling glob matched nothing and silently contributed zero rules while the # Frameworks line still advertised WAF coverage. Build-AssessmentCatalog.ps1 -Check # now fails on a dangling pattern so this cannot recur. Rules = @('caf.storage'); Frameworks = @('CAF: Security') Tags = @('caf', 'waf', 'storage'); Reporters = @('Html', 'Excel') } 'Assess: Databases' = @{ Description = 'SQL/DB private access, TDE, zone redundancy' Category = 'Databases'; Collect = @('Databases'); Ingest = @() Rules = @('caf.databases'); Frameworks = @('CAF: Security', 'WAF: Reliability') Tags = @('caf', 'databases'); Reporters = @('Html', 'Excel') } 'Assess: Containers' = @{ Description = 'AKS private clusters, RBAC, registry hardening' Category = 'Containers'; Collect = @('Containers'); Ingest = @() Rules = @('caf.containers'); Frameworks = @('CAF: Security', 'WAF: Reliability') Tags = @('caf', 'containers'); Reporters = @('Html', 'Excel') } 'Assess: Web' = @{ Description = 'App Service HTTPS-only, TLS, managed identity' Category = 'Web'; Collect = @('Web'); Ingest = @() Rules = @('caf.web'); Frameworks = @('CAF: Security', 'WAF: Security') Tags = @('caf', 'web'); Reporters = @('Html', 'Excel') } 'Assess: Analytics' = @{ Description = 'Analytics data governance and network isolation' Category = 'Analytics'; Collect = @('Analytics'); Ingest = @() Rules = @('caf.analytics'); Frameworks = @('CAF: Governance', 'WAF: Security') Tags = @('caf', 'analytics'); Reporters = @('Html', 'Excel') } 'Assess: AI' = @{ Description = 'AI/Cognitive private access and responsible-AI posture' Category = 'AI'; Collect = @('AI'); Ingest = @() Rules = @('caf.ai'); Frameworks = @('CAF: Governance', 'WAF: Security') Tags = @('caf', 'ai'); Reporters = @('Html', 'Excel') } 'Assess: Integration' = @{ Description = 'Messaging redundancy and APIM network isolation' Category = 'Integration'; Collect = @('Integration'); Ingest = @() Rules = @('caf.integration'); Frameworks = @('CAF: Network topology & connectivity', 'WAF: Reliability') Tags = @('caf', 'integration'); Reporters = @('Html', 'Excel') } 'Assess: Hybrid' = @{ Description = 'Arc onboarding, agent currency, Azure Local' Category = 'Hybrid'; Collect = @('Hybrid'); Ingest = @() Rules = @('caf.hybrid'); Frameworks = @('CAF: Management & monitoring', 'WAF: Operational excellence') Tags = @('caf', 'hybrid'); Reporters = @('Html', 'Excel') } 'Assess: IoT' = @{ Description = 'IoT Hub/DPS network isolation and device auth' Category = 'IoT'; Collect = @('IoT'); Ingest = @() Rules = @('caf.iot'); Frameworks = @('CAF: Security', 'WAF: Security') Tags = @('caf', 'iot'); Reporters = @('Html', 'Excel') } # ---- WAF workload-specific reviews (Feature AB#6748, Epic AB#6454) ---- # # Neither of these is a WAF pillar or CAF design area -- both cut across several. See # src/assess/rules/waf.ai.yaml and waf.avd.yaml for why each declares its own `framework` # value (WAF-AI / WAF-AVD) rather than `WAF`, and docs/frameworks/waf-ai-workload-checklist.md # / waf-avd-workload-checklist.md for what each enumeration does and does not cover. 'Assess: AI Workload' = @{ Description = 'AI workload review (Well-Architected Framework AI workload guidance) -- 34 items across 7 of 10 AI design areas; mostly manual, see docs/frameworks/waf-ai-workload-checklist.md' Category = 'AI'; Collect = @('AI'); Ingest = @() Rules = @('waf.ai'); Frameworks = @('WAF: AI workload') Tags = @('waf', 'ai', 'workload-review'); Reporters = @('Html', 'Excel') } 'Assess: AVD Workload' = @{ # AVD-on-Azure-Local scope only -- see docs/frameworks/waf-avd-workload-checklist.md # ("The gap this leaves") for what general (non-Azure-Local) AVD is NOT covered by this # entry. Description = 'AVD-on-Azure-Local workload review (Well-Architected Framework) -- 20 items across all 5 pillars; scoped to AVD deployed on Azure Local, not general Azure Virtual Desktop' Category = 'Compute'; Collect = @('Compute', 'Storage'); Ingest = @() Rules = @('waf.avd'); Frameworks = @('WAF: AVD workload (Azure Local)') Tags = @('waf', 'avd', 'azure-local', 'workload-review'); Reporters = @('Html', 'Excel') } # ---- finer sub-bundles inside a category ---- # 'Policy' used to sit here too, byte-identical to 'Governance' (same Category, Collect, # Ingest, Rules, Frameworks — literally the same assessment under two names). Removed by # AB#6795; script an explicit -Assessment Governance instead of -Assessment Policy. Governance = @{ Description = 'Management sub-bundle — policy assignments, locks, budgets' Category = 'Management'; Collect = @('Management'); Ingest = @('Governance') Rules = @('caf.governance'); Frameworks = @('CAF: Governance'); Tags = @('caf', 'governance', 'sub-bundle'); Reporters = @('Html') } # UpdateManager and Monitoring are each a STRICT SUBSET of a broader entry above # ('Assess: Management' and 'Assess: Monitor' respectively) -- same rule file, same Category, # same Collect list, just offered again under a narrower name. AB#6795 requires a subset to # say so; the description now names the entry it is a subset of rather than leaving that # implicit in the rule-file overlap. UpdateManager = @{ Description = 'Management sub-bundle (subset of "Assess: Management") — patch/update compliance only' Category = 'Management'; Collect = @('Management'); Ingest = @() Rules = @('caf.management'); Frameworks = @('WAF: Operational excellence'); Tags = @('waf', 'update-manager', 'sub-bundle'); Reporters = @('Html') } Monitoring = @{ Description = 'Monitor sub-bundle (subset of "Assess: Monitor") — diagnostic settings coverage only' Category = 'Monitor'; Collect = @('Monitor'); Ingest = @() Rules = @('waf.operational'); Frameworks = @('WAF: Operational excellence'); Tags = @('waf', 'monitoring', 'sub-bundle'); Reporters = @('Html') } # ---- regulatory / benchmark compliance (Epic AB#6454, Feature AB#6744) ---- # Reads compliance state Azure Policy already evaluated (Scout already collects it -- see # domains.management.policyComplianceStates / policyInitiatives) rather than asserting # anything itself. `Compliance = $true` routes this entry through the compliance engine # (Invoke-ScoutComplianceAssessment) instead of the YAML rule engine -- see # src/assess/engine/Get-ScoutComplianceScore.ps1 and docs/design/decisions/declarative-collectors.md # for why this needed a small, explicit core-engine branch rather than a rule file: a rule # asserts against one JSONPath; MCSB/CIS/ISO/NIST/PCI are ~200 already-scored controls apiece, # and hand-writing YAML for every one of them would duplicate work Azure has already done and # drift from Microsoft's own control mapping on every framework revision (AB#6792/AB#6794). # # `Rules = @('compliance.*')` exists ONLY so Get-ScoutAvailableAssessment's evidence-based # menu gate (AB#6763) has a file to match -- src/assess/rules/compliance.initiative.yaml # carries no `rules:` list, it is a marker. The reuse is deliberate: AB#6763 already solved # "don't offer an entry that scores nothing"; this is the same mechanism, not a new one. # # One menu entry expands into ONE scored Framework card per initiative actually ASSIGNED in # the scanned scope (AB#6794) -- MCSB always (Defender's default initiative), plus whichever # of CIS/ISO/NIST/PCI/etc are assigned, each carrying its own exact version in the Framework # name so two versions of the same initiative never merge into one score (AB#6792/AB#6794). An # unassigned initiative produces no card at all -- see AB#6793 for why "not assessed" must # never collapse into a percentage. 'Assess: Compliance' = @{ Description = 'Regulatory compliance — scores every Azure Policy regulatory-compliance initiative assigned in the scanned scope (MCSB, CIS, ISO 27001, NIST, PCI-DSS, ...) from compliance state Azure already evaluated' Category = 'Management' Collect = @('Management') Ingest = @() Rules = @('compliance.*') Frameworks = @('CAF: Govern', 'CAF: Secure') Tags = @('compliance', 'policy', 'regulatory') Compliance = $true RequiresData = @( '$.domains.management.policyComplianceStates[*]' ) Reporters = @('Html', 'Excel') } # ---- cloud governance maturity report (Feature AB#6458, Story AB#6459, Epic AB#6454) ---- # The seven caf.govern.*.yaml rule files (Regulatory Compliance, Security, Cost # Management, Operations, Data, Resource Management, AI) are CAF Govern's seven risk # categories per docs/frameworks/cloud-governance-question-set.md (AB#6811) -- the # methodology behind the interactive "Cloud Governance" assessment, distinct from # caf.governance.yaml's Governance DESIGN AREA (already scored under 'CAF: Governance' # above). GovernanceReport is the consultant-grade deliverable: a 1-10 domain maturity # score per risk category (Get-GovernanceDomainScore.ps1, deliberately NOT the same # scale as WAF's published 5-level maturity model -- see # docs/design/governance-domain-maturity-scale.md), a radar chart, and a domain x status # heatmap, reusing the vendored offline ECharts library Export-EChartsDashboard.ps1 # already carries rather than a second charting mechanism. 'Assess: Cloud Governance' = @{ Description = 'CAF Govern methodology -- 1-10 maturity score per risk category (regulatory compliance, security, cost, operations, data, resource management, AI), radar + heatmap report' Category = 'Management' Collect = @('Management') Ingest = @('Governance', 'AdvisorScores') Rules = @('caf.govern.*') Frameworks = @('CAF: Govern') Tags = @('caf', 'governance', 'maturity') Reporters = @('GovernanceReport', 'Html', 'Excel') } # ---- migration readiness (AB#6832) ---- # RequiresData is what keeps this out of the wizard's menu until the Migration collectors # actually return rows: the wizard resolves these paths against the most recent collect.json # and hides the entry when none of them has data. The rule file carries the same prerequisite # (`requires:`), so a direct -Assessment SMART run on an empty estate reports Unknown rather # than a manufactured pass. Both halves are needed -- the menu gate is a courtesy, the rule # gate is the correctness guarantee. SMART = @{ Description = 'Strategic Migration Assessment — migration readiness (see docs/frameworks/smart-question-set.md)' Category = 'Migration' Collect = @('Migration', 'Management', 'Security', 'Compute') Ingest = @('Governance') Rules = @('smart.*') Frameworks = @('CAF: Migrate', 'SMART: readiness') Tags = @('caf', 'migration', 'smart') RequiresData = @( '$.domains.migration.migrateProjects[*]' '$.domains.migration.discoverySites[*]' '$.domains.migration.migrationServices[*]' ) Reporters = @('Html', 'Excel') } # ---- Azure VMware Solution (AB#6820, Feature AB#6748, Epic AB#6454) ---- # Two assessments, one prerequisite: neither means anything on an estate with no AVS private # cloud, so both gate on the same RequiresData path the AVS.workload.yaml / caf.avslandingzone # rule files' own `requires:` block repeats (AB#6832's pattern) -- the wizard menu hides the # entry, and a direct -Assessment run on an empty estate reports Unknown rather than a # manufactured pass either way. 'AVS Workload' = @{ Description = 'Azure VMware Solution workload — Reliability, Security, and Governance coverage (no published WAF pillar service guide exists for AVS; see docs/frameworks/waf-avs-workload-checklist.md)' Category = '*' Collect = @('*') Ingest = @('Governance') Rules = @('avs.workload') Frameworks = @('AVS: Reliability, Security, Governance (not full WAF pillar coverage)') Tags = @('avs', 'reliability', 'security', 'governance') RequiresData = @( '$.compute.privateClouds[*]' ) Reporters = @('Html', 'Excel') } 'AVS Landing Zone' = @{ Description = 'Azure VMware Solution Landing Zone Assessment Review — platform readiness (see docs/frameworks/avs-landing-zone-question-set.md)' Category = '*' Collect = @('*') Ingest = @('Governance') Rules = @('caf.avslandingzone') Frameworks = @('CAF: Resource organization (service guide)') Tags = @('caf', 'avs', 'landing-zone') RequiresData = @( '$.compute.privateClouds[*]' ) Reporters = @('Html', 'Excel') } # ---- Cloud Adoption Security Assessment (AB#6821, Feature AB#6748, Epic AB#6454) ---- # Tenant-wide, unlike the two AVS entries above -- CASA scores general cloud security # maturity, not a specific workload, so it carries no RequiresData gate; an estate with zero # role assignments or zero key vaults is a genuine (if unusual) finding, not a signal the # assessment does not apply. Description says plainly that the question text is inferred, per # docs/frameworks/casa-question-set.md's own header. CASA = @{ Description = 'Cloud Adoption Security Assessment — cloud security maturity aligned to the CAF Secure methodology (question text is Scout''s own inference from the published CAF Secure checklist, not Microsoft''s numbered CASA questions; see docs/frameworks/casa-question-set.md)' Category = '*' Collect = @('*') Ingest = @('Governance') Rules = @('casa.*') Frameworks = @('CASA: 7 CAF Secure domains') Tags = @('casa', 'security', 'caf-secure') Reporters = @('Html', 'Excel') } # ---- FinOps Review / DevOps Capability Assessment (AB#6826/AB#6827, Feature AB#6749) ---- # # Both enumerations are INFERRED, not Microsoft-published -- see the Description below and # each rule file's own header before quoting a coverage figure from either. Both assessments # degrade the same way when their gated data source is unavailable: `assert.gate` on the # affected rules reports NotAssessed, never a scored zero (Invoke-Rule.ps1, AB#6826). 'FinOps Review' = @{ Description = 'FinOps Review -- scores against the FinOps Framework''s 22 published capabilities (docs/frameworks/finops-review-question-set.md). The assessment itself and its question numbering are INFERRED, not Microsoft-published -- Microsoft names the assessment and publishes the framework, but not the assessment''s own question text. Cost data sits behind the EA/MCA billing permission system, a different boundary than ARM Reader; when that gate blocks the pull, the affected findings report NotAssessed, never a scored zero.' Category = '*' Collect = @('FinOps', 'Cost', 'Management') Ingest = @('Governance', 'AdvisorScores', 'CostInventory') Rules = @('finops.review') Frameworks = @('FinOps: 22 capabilities') Tags = @('finops', 'cost', 'inferred-enumeration') Reporters = @('Html', 'Excel') } 'DevOps Capability Assessment' = @{ Description = 'DevOps Capability Assessment -- scores against the Microsoft DevOps Resource Center''s five practice phases (docs/frameworks/devops-capability-question-set.md). The assessment itself and its question numbering are INFERRED, not Microsoft-published. A DIFFERENT, narrower assessment than "CAF: Platform automation and DevOps" (the landing-zone design area) -- the two overlap in subject but are not the same enumeration. Azure DevOps access is opt-in (-IncludeDevOps) and sits behind its own auth boundary; when it was not granted, the affected findings report NotAssessed, never a scored zero.' Category = '*' Collect = @('DevOps', 'Management') Ingest = @('Governance', 'DevOpsCapability') Rules = @('devops.capability') Frameworks = @('DevOps Capability: 5 phases') Tags = @('devops', 'inferred-enumeration') Reporters = @('Html', 'Excel') } # ---- cross-resource correlation (AB#6835) ---- # Every rule here spans TWO datasets, so Collect must gather both halves or a rule silently # passes on an empty right-hand side. Both categories of every pair are listed deliberately. CrossResource = @{ Description = 'Findings that require two collected datasets correlated' Category = '*' Collect = @('Compute', 'Storage', 'Security', 'Networking', 'Management') Ingest = @() Rules = @('xr.*') Frameworks = @('XR: Cross-resource posture') Tags = @('cross-resource', 'waf', 'caf') Reporters = @('Html', 'Excel') } # ---- targeted cost pull ---- Cost = @{ Description = 'Cost / TCO data pull' Category = '*'; Collect = @('Cost', 'Compute', 'Storage'); Ingest = @('AdvisorScores') Rules = @('waf.cost'); Frameworks = @('WAF: Cost optimization'); Tags = @('waf', 'cost'); Reporters = @('Excel', 'PowerBi') } # ---- AB#6796 (Feature AB#6746, Epic AB#6454) — WAF split into its five pillars ---- # # LandingZone's WAF framework score is a weighted average of these same five area scores # (Get-Score, AB#5087), so it reconciles with the sum of these five assessments by # construction: run LandingZone once, or run all five WAF pillar assessments and combine # them, and the numbers agree because it is the same arithmetic either way. 'WAF: Reliability' = @{ Description = 'Well-Architected Framework — Reliability pillar only' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.reliability'); Frameworks = @('WAF: Reliability'); Tags = @('waf', 'pillar', 'reliability'); Reporters = @('Html', 'Excel') } 'WAF: Security' = @{ Description = 'Well-Architected Framework — Security pillar only' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.security'); Frameworks = @('WAF: Security'); Tags = @('waf', 'pillar', 'security'); Reporters = @('Html', 'Excel') } 'WAF: Cost Optimization' = @{ Description = 'Well-Architected Framework — Cost Optimization pillar only' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.cost'); Frameworks = @('WAF: Cost Optimization'); Tags = @('waf', 'pillar', 'cost'); Reporters = @('Html', 'Excel') } 'WAF: Operational Excellence' = @{ Description = 'Well-Architected Framework — Operational Excellence pillar only' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.operational'); Frameworks = @('WAF: Operational Excellence'); Tags = @('waf', 'pillar', 'operational'); Reporters = @('Html', 'Excel') } 'WAF: Performance Efficiency' = @{ Description = 'Well-Architected Framework — Performance Efficiency pillar only' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.performance'); Frameworks = @('WAF: Performance Efficiency'); Tags = @('waf', 'pillar', 'performance'); Reporters = @('Html', 'Excel') } # ---- AB#6803 (Feature AB#6747, Epic AB#6454) — Azure Local Well-Architected Review ---- # # Every rule cites a WAF-AZLOCAL-* item from docs/frameworks/waf-azure-local-checklist.md (33 # items across the five WAF pillars, enumerated 2026-08-01), spread across FIVE rule files # (waf.azurelocal.reliability/security/cost/operational/performance.yaml), one per pillar -- # tests/Assessment.Restructure.Tests.ps1's structural gate requires a `framework: WAF` file's # `area` to be exactly one of the five real pillars, with no service-guide exception for WAF # (that escape hatch is CAF-only, see caf.hybrid.yaml/caf.ai.yaml's `kind: service-guide`). # A per-workload review spanning all five pillars therefore gets five small, honestly-scoped # files rather than one file claiming a sixth "workload" area the gate would reject. # # RequiresData is the AC's product-safety gate, not a per-rule data dependency: NEITHER # `Hybrid/ArcSites` nor `Hybrid/VirtualMachines` has ever returned a row in any tenant this # session could reach (AB#6801/AB#6802 -- both were re-sourced off ARM REST because Resource # Graph does not index either type), so this entry stays out of the wizard menu until at # least one of them proves it can. `Get-ScoutAvailableAssessment`'s existing OR-across-paths # semantics (the same the SMART entry above uses) mean either collector returning rows is # enough to surface the entry -- reused exactly as documented, not a parallel gate. # `RequiresAzureLocalArm = $true` is read by Invoke-ScoutAssessmentCore.ps1 to opt this one # assessment's collect into `-IncludeAzureLocalArm` (see Invoke-Collect.ps1), the only way # either dataset is ever populated -- every other assessment's collect leaves both empty by # design, so this flag and this RequiresData gate are two ends of the same fact. # # STATICALLY PROVEN this session: the gate mechanism itself (empty domains.hybrid.arcSites / # domains.hybrid.azureLocalVirtualMachineInstances hides the entry; Pester fixtures with rows # present show it), and that every rule's query resolves against a real, exercised collect.json # shape. NOT provable without a live Azure Local tenant: that -IncludeAzureLocalArm's REST # calls actually return non-empty rows against real Microsoft.Edge/sites and # Microsoft.AzureStackHCI/virtualMachineInstances endpoints -- see AB#6843 (live per-collector # verification) for that half. 'WAF: Azure Local' = @{ Description = 'Well-Architected Framework — Azure Local (platform 2311+ and Azure Local VMs) workload review' Category = '*' Collect = @('Hybrid', 'Compute') Ingest = @('Governance') # Five files, not one -- tests/Assessment.Restructure.Tests.ps1's structural gate # requires a framework:WAF file's `area` to be exactly one of the five real pillars, with # no service-guide exception for WAF. See waf.azurelocal.reliability.yaml's header. Rules = @('waf.azurelocal.*') Frameworks = @('WAF: Azure Local (service guide, all 5 pillars)') Tags = @('waf', 'azure-local', 'hci', 'service-guide') RequiresAzureLocalArm = $true RequiresData = @( '$.domains.hybrid.arcSites[*]' '$.domains.hybrid.azureLocalVirtualMachineInstances[*]' ) Reporters = @('Html', 'Excel') } # ---- AB#6800 (Feature AB#6746, Epic AB#6454) — WAF Maturity Model ---- # # Reuses the exact same five WAF pillar rule files as the five entries above and # LandingZone -- no duplicated rule definitions (AC). Get-Score attaches a MaturityLevel # (Microsoft's published 5-level model) alongside each area/framework percentage score # whenever Get-MaturityLevel.ps1 is loaded, which the module does unconditionally # (AzureScout.psm1 loads every src/**/*.ps1). See docs/design/waf-maturity-model-mapping.md. 'WAF: Maturity Model' = @{ Description = 'Well-Architected Framework — maturity levels per pillar (same rules as the five WAF pillar assessments, different output framing)' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('waf.reliability', 'waf.security', 'waf.cost', 'waf.operational', 'waf.performance') Frameworks = @('WAF: all 5 pillars, presented as maturity levels') Tags = @('waf', 'maturity-model'); Reporters = @('Html', 'Excel') } # ---- AB#6797 (Feature AB#6746, Epic AB#6454) — CAF split into its eight design areas ---- # # LandingZone's CAF framework score is a weighted average of these eight area scores # (Get-Score, AB#5087, weights documented in docs/design/caf-design-area-weighting.md), so # it reconciles with these eight assessments by construction the same way the WAF split # does above. 'CAF: Azure billing and Microsoft Entra tenant' = @{ Description = 'CAF landing zone design area — Azure billing and Microsoft Entra ID tenant setup' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.billing'); Frameworks = @('CAF: Azure billing and Microsoft Entra tenant'); Tags = @('caf', 'design-area', 'billing'); Reporters = @('Html', 'Excel') } 'CAF: Identity and access management' = @{ Description = 'CAF landing zone design area — Identity and access management' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.identity'); Frameworks = @('CAF: Identity and access management'); Tags = @('caf', 'design-area', 'identity'); Reporters = @('Html', 'Excel') } 'CAF: Resource organization' = @{ Description = 'CAF landing zone design area — Resource organization (management groups, subscriptions, tags)' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.resourceorg'); Frameworks = @('CAF: Resource organization'); Tags = @('caf', 'design-area', 'resource-organization'); Reporters = @('Html', 'Excel') } 'CAF: Network topology and connectivity' = @{ Description = 'CAF landing zone design area — Network topology and connectivity' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.network'); Frameworks = @('CAF: Network topology and connectivity'); Tags = @('caf', 'design-area', 'network'); Reporters = @('Html', 'Excel') } 'CAF: Security' = @{ Description = 'CAF landing zone design area — Security' Category = '*'; Collect = @('*'); Ingest = @('Governance', 'AdvisorScores') Rules = @('caf.security'); Frameworks = @('CAF: Security'); Tags = @('caf', 'design-area', 'security'); Reporters = @('Html', 'Excel') } 'CAF: Management' = @{ Description = 'CAF landing zone design area — Management (monitoring, operations baseline)' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.management'); Frameworks = @('CAF: Management'); Tags = @('caf', 'design-area', 'management'); Reporters = @('Html', 'Excel') } 'CAF: Governance' = @{ Description = 'CAF landing zone design area — Governance (policy & compliance)' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.governance'); Frameworks = @('CAF: Governance'); Tags = @('caf', 'design-area', 'governance'); Reporters = @('Html', 'Excel') } 'CAF: Platform automation and DevOps' = @{ Description = 'CAF landing zone design area — Platform automation and DevOps' Category = '*'; Collect = @('*'); Ingest = @('Governance') Rules = @('caf.platformauto'); Frameworks = @('CAF: Platform automation and DevOps'); Tags = @('caf', 'design-area', 'platform-automation'); Reporters = @('Html', 'Excel') } } |