m365-governance-as-code 1.0.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. m365_governance_as_code-1.0.0b1/LICENSE +21 -0
  2. m365_governance_as_code-1.0.0b1/PKG-INFO +492 -0
  3. m365_governance_as_code-1.0.0b1/README.md +465 -0
  4. m365_governance_as_code-1.0.0b1/pyproject.toml +111 -0
  5. m365_governance_as_code-1.0.0b1/setup.cfg +4 -0
  6. m365_governance_as_code-1.0.0b1/src/m365_governance/__init__.py +3 -0
  7. m365_governance_as_code-1.0.0b1/src/m365_governance/assessment.py +325 -0
  8. m365_governance_as_code-1.0.0b1/src/m365_governance/attention.py +272 -0
  9. m365_governance_as_code-1.0.0b1/src/m365_governance/classifying.py +139 -0
  10. m365_governance_as_code-1.0.0b1/src/m365_governance/cli.py +796 -0
  11. m365_governance_as_code-1.0.0b1/src/m365_governance/collecting.py +264 -0
  12. m365_governance_as_code-1.0.0b1/src/m365_governance/comparison.py +163 -0
  13. m365_governance_as_code-1.0.0b1/src/m365_governance/data/__init__.py +16 -0
  14. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/Get-SpoEvidence.ps1 +478 -0
  15. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Activity.psm1 +148 -0
  16. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Agents.psm1 +184 -0
  17. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Classification.psm1 +176 -0
  18. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Connection.psm1 +104 -0
  19. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Evidence.psm1 +174 -0
  20. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Modernity.psm1 +128 -0
  21. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Permissions.psm1 +144 -0
  22. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Sharing.psm1 +135 -0
  23. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Sites.psm1 +112 -0
  24. m365_governance_as_code-1.0.0b1/src/m365_governance/data/collectors/powershell/sharepoint/modules/Spfx.psm1 +194 -0
  25. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/README.md +50 -0
  26. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/archive/evidence-1.2.0-site.json +48 -0
  27. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/assessment/classification.json +416 -0
  28. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/assessment/three-resources.json +2218 -0
  29. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/comparison/sharing-mitigated.json +62 -0
  30. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-catalog-over-hard-limit.json +119 -0
  31. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-application.json +111 -0
  32. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-catalog.json +111 -0
  33. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-content.json +111 -0
  34. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-system-and-application.json +111 -0
  35. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-system.json +111 -0
  36. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-class-unknown.json +69 -0
  37. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-count-invalid.json +69 -0
  38. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-count-not-collected.json +69 -0
  39. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-inheritance-not-collected.json +69 -0
  40. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-over-limit.json +69 -0
  41. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-scopes-counted-in-part.json +78 -0
  42. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-scopes-not-counted.json +73 -0
  43. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-scopes-over-hard-limit.json +77 -0
  44. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-scopes-over-recommended.json +77 -0
  45. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-scopes-within-recommended.json +77 -0
  46. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-unique-permissions.json +69 -0
  47. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/list-within-limit.json +69 -0
  48. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-activity-archived.json +105 -0
  49. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-activity-locked.json +105 -0
  50. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-activity-not-read.json +69 -0
  51. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-activity-recent.json +105 -0
  52. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-activity-stale.json +105 -0
  53. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-agents-with-sources.json +121 -0
  54. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-group-unlabelled.json +109 -0
  55. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-invalid.json +101 -0
  56. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-label-invalid.json +117 -0
  57. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-label-unresolved.json +113 -0
  58. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-labelled.json +121 -0
  59. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-legacy-string.json +109 -0
  60. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-not-read.json +89 -0
  61. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-observed-20260810.json +109 -0
  62. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-string-and-label.json +125 -0
  63. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-class-unlabelled.json +105 -0
  64. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-delegated-identity.json +63 -0
  65. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-groups-only-admins.json +85 -0
  66. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-imported-inventory.json +62 -0
  67. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-modern-clean.json +157 -0
  68. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-modern-custom-master.json +157 -0
  69. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-modern-not-read.json +81 -0
  70. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-modern-publishing-on.json +161 -0
  71. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-modern-subsite-default-master.json +157 -0
  72. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-named-and-group-admins.json +87 -0
  73. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-one-owner.json +59 -0
  74. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-owners-not-collected.json +56 -0
  75. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-partial-expansion-decides.json +75 -0
  76. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-partial-expansion-undecided.json +67 -0
  77. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-anyone-default-anyone.json +81 -0
  78. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-anyone-default-direct.json +81 -0
  79. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-default-permission-edit.json +81 -0
  80. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-default-permission-inherits.json +81 -0
  81. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-default-permission-view.json +81 -0
  82. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-guests-only.json +81 -0
  83. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-inherits-tenant-default.json +77 -0
  84. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-sharing-not-collected.json +64 -0
  85. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-spfx-behind.json +119 -0
  86. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-spfx-current.json +101 -0
  87. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-spfx-no-catalog.json +61 -0
  88. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-spfx-pages-unreconciled.json +93 -0
  89. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-storage-comfortable.json +73 -0
  90. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-storage-nearly-full.json +73 -0
  91. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-storage-no-quota.json +69 -0
  92. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/site-two-owners.json +63 -0
  93. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/tenant-sharing-default-anyone-and-edit.json +69 -0
  94. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/tenant-sharing-internal-only.json +65 -0
  95. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/tenant-sharing-mitigated.json +69 -0
  96. m365_governance_as_code-1.0.0b1/src/m365_governance/data/fixtures/sharepoint/tenant-sharing-observed-20260808.json +69 -0
  97. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/csharp/Assessment.g.cs +103 -0
  98. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/csharp/Comparison.g.cs +86 -0
  99. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/csharp/Evidence.g.cs +148 -0
  100. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/csharp/Run.g.cs +143 -0
  101. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/csharp/RunSet.g.cs +54 -0
  102. m365_governance_as_code-1.0.0b1/src/m365_governance/data/generated/manifest.json +83 -0
  103. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/activity.yaml +17 -0
  104. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/capacity.yaml +27 -0
  105. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/classification.yaml +30 -0
  106. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/default.yaml +26 -0
  107. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/modernisation.yaml +22 -0
  108. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/ownership.yaml +18 -0
  109. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/sharing.yaml +21 -0
  110. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/spfx.yaml +33 -0
  111. m365_governance_as_code-1.0.0b1/src/m365_governance/data/profiles/tenant-sharing.yaml +21 -0
  112. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-ACTIVITY-001.yaml +109 -0
  113. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-CLASS-001.yaml +118 -0
  114. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-CLASS-002.yaml +110 -0
  115. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-CLASS-003.yaml +129 -0
  116. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-CLASS-004.yaml +119 -0
  117. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-LIST-001.yaml +126 -0
  118. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-LIST-002.yaml +105 -0
  119. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-LIST-003.yaml +97 -0
  120. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-MODERN-001.yaml +102 -0
  121. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-MODERN-003.yaml +88 -0
  122. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-MODERN-004.yaml +79 -0
  123. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SHARE-001.yaml +96 -0
  124. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SHARE-002.yaml +105 -0
  125. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SHARE-003.yaml +118 -0
  126. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SHARE-004.yaml +114 -0
  127. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SHARE-005.yaml +110 -0
  128. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SITE-001.yaml +89 -0
  129. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SITE-002.yaml +88 -0
  130. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SITE-003.yaml +99 -0
  131. m365_governance_as_code-1.0.0b1/src/m365_governance/data/rules/sharepoint/SPO-SPFX-001.yaml +85 -0
  132. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/assessment-2.0.0.schema.json +236 -0
  133. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/assessment-3.0.0.schema.json +236 -0
  134. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/comparison-2.0.0.schema.json +266 -0
  135. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/evidence-1.2.0.schema.json +511 -0
  136. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/evidence-2.0.0.schema.json +559 -0
  137. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/run-2.0.0.schema.json +254 -0
  138. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/run-3.0.0.schema.json +379 -0
  139. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/run-set-2.0.0.schema.json +56 -0
  140. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/archive/run-set-3.0.0.schema.json +56 -0
  141. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/assessment.schema.json +236 -0
  142. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/comparison.schema.json +266 -0
  143. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/evidence.schema.json +602 -0
  144. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/rule.schema.json +436 -0
  145. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/run-set.schema.json +56 -0
  146. m365_governance_as_code-1.0.0b1/src/m365_governance/data/schemas/run.schema.json +367 -0
  147. m365_governance_as_code-1.0.0b1/src/m365_governance/diffing.py +446 -0
  148. m365_governance_as_code-1.0.0b1/src/m365_governance/doctor.py +263 -0
  149. m365_governance_as_code-1.0.0b1/src/m365_governance/engine.py +356 -0
  150. m365_governance_as_code-1.0.0b1/src/m365_governance/explaining.py +283 -0
  151. m365_governance_as_code-1.0.0b1/src/m365_governance/identity.py +69 -0
  152. m365_governance_as_code-1.0.0b1/src/m365_governance/inspect.py +300 -0
  153. m365_governance_as_code-1.0.0b1/src/m365_governance/loader.py +97 -0
  154. m365_governance_as_code-1.0.0b1/src/m365_governance/registry.py +288 -0
  155. m365_governance_as_code-1.0.0b1/src/m365_governance/reporting.py +843 -0
  156. m365_governance_as_code-1.0.0b1/src/m365_governance/resources.py +115 -0
  157. m365_governance_as_code-1.0.0b1/src/m365_governance/results.py +316 -0
  158. m365_governance_as_code-1.0.0b1/src/m365_governance/validator.py +257 -0
  159. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/PKG-INFO +492 -0
  160. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/SOURCES.txt +184 -0
  161. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/dependency_links.txt +1 -0
  162. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/entry_points.txt +2 -0
  163. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/requires.txt +2 -0
  164. m365_governance_as_code-1.0.0b1/src/m365_governance_as_code.egg-info/top_level.txt +1 -0
  165. m365_governance_as_code-1.0.0b1/tests/test_assessment.py +344 -0
  166. m365_governance_as_code-1.0.0b1/tests/test_attention.py +496 -0
  167. m365_governance_as_code-1.0.0b1/tests/test_bounded.py +116 -0
  168. m365_governance_as_code-1.0.0b1/tests/test_cli_and_reporting.py +250 -0
  169. m365_governance_as_code-1.0.0b1/tests/test_collector.py +480 -0
  170. m365_governance_as_code-1.0.0b1/tests/test_commands.py +1269 -0
  171. m365_governance_as_code-1.0.0b1/tests/test_comparison.py +173 -0
  172. m365_governance_as_code-1.0.0b1/tests/test_contracts_command.py +138 -0
  173. m365_governance_as_code-1.0.0b1/tests/test_coverage_vocabulary.py +91 -0
  174. m365_governance_as_code-1.0.0b1/tests/test_decisions.py +325 -0
  175. m365_governance_as_code-1.0.0b1/tests/test_engine.py +720 -0
  176. m365_governance_as_code-1.0.0b1/tests/test_evidence_schema.py +206 -0
  177. m365_governance_as_code-1.0.0b1/tests/test_external_facts.py +215 -0
  178. m365_governance_as_code-1.0.0b1/tests/test_external_sources.py +246 -0
  179. m365_governance_as_code-1.0.0b1/tests/test_generator.py +106 -0
  180. m365_governance_as_code-1.0.0b1/tests/test_identity.py +319 -0
  181. m365_governance_as_code-1.0.0b1/tests/test_loader.py +64 -0
  182. m365_governance_as_code-1.0.0b1/tests/test_registry.py +319 -0
  183. m365_governance_as_code-1.0.0b1/tests/test_resources.py +154 -0
  184. m365_governance_as_code-1.0.0b1/tests/test_rules_validate.py +259 -0
  185. m365_governance_as_code-1.0.0b1/tests/test_run_schema.py +103 -0
  186. m365_governance_as_code-1.0.0b1/tests/test_runset.py +354 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pH7x Systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,492 @@
1
+ Metadata-Version: 2.4
2
+ Name: m365-governance-as-code
3
+ Version: 1.0.0b1
4
+ Summary: Microsoft 365 governance checks that show their work.
5
+ Author-email: pH7x Systems <support@ph7x.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://ph7x.com/tools/m365-governance-as-code/
8
+ Project-URL: Documentation, https://ph7x.com/tools/m365-governance-as-code/docs/
9
+ Project-URL: Repository, https://github.com/ph7x-Systems/m365-governance-as-code
10
+ Project-URL: Issues, https://github.com/ph7x-Systems/m365-governance-as-code/issues
11
+ Project-URL: Changelog, https://github.com/ph7x-Systems/m365-governance-as-code/blob/main/CHANGELOG.md
12
+ Keywords: microsoft-365,sharepoint,governance,policy-as-code
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: System :: Systems Administration
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: PyYAML>=6.0
25
+ Requires-Dist: jsonschema>=4.20
26
+ Dynamic: license-file
27
+
28
+ ![Microsoft 365 Governance as Code, by pH7x Systems](docs/banner.png)
29
+
30
+ # Microsoft 365 Governance as Code
31
+
32
+
33
+ > **Every governance conclusion must declare what kind of truth it is.**
34
+ >
35
+ > **Automation may verify a claim. It may never strengthen it.**
36
+
37
+ Governance checks that show their work. PowerShell collects facts. Python
38
+ evaluates declarative rules. Every finding says what kind of claim it is,
39
+ which evidence it came from, and what it does not establish.
40
+
41
+ Nothing here changes anything in a tenant. There is no write path, no
42
+ remediation command, and no `--fix-all`.
43
+
44
+ **Scope.** This project evaluates the Microsoft 365 tenant that exists today.
45
+ It does not infer the characteristics of an estate it has not observed. There
46
+ are two modes, and the engine never knows which one produced the evidence it
47
+ is reading: **Live** observes Microsoft 365 directly, **Assessment** evaluates
48
+ evidence exported by something else and says so on the first line of every
49
+ report. See [docs/SCOPE.md](docs/SCOPE.md).
50
+
51
+ **Why it exists.** We kept writing the same governance findings into
52
+ documents, and kept watching them lose the one thing that made them useful:
53
+ whether the finding was a rule Microsoft enforces, a limit it imposes, advice
54
+ it gives, or our own opinion. This project is that distinction, made
55
+ executable and made impossible to skip.
56
+
57
+ **Status:** `1.0.0-beta.1`. 18 rules, a ten-mode collector validated
58
+ against a live tenant, 8 profiles, ten commands and 350 tests at 91 per
59
+ cent coverage. Beta because the model has stopped moving: the outcomes, the
60
+ resolution order, the basis types and the evidence schema are frozen, and
61
+ [docs/MILESTONE-A.md](docs/MILESTONE-A.md) records what closing SharePoint
62
+ end to end actually cost. The rule set is still small, and that is the next
63
+ milestone rather than a caveat on this one.
64
+
65
+ ---
66
+
67
+ ## The problem
68
+
69
+ Most governance tooling produces a green box. A green box has no grammar for
70
+ the difference between these four sentences:
71
+
72
+ - the product **enforces** this;
73
+ - the product **imposes** this boundary, and here is the number;
74
+ - Microsoft **recommends** this, and permits the alternative;
75
+ - **we think** this is a good idea.
76
+
77
+ Those four produce different conversations, different budgets and different
78
+ arguments with an auditor. A tool that renders them identically has removed
79
+ the only information the reader needed.
80
+
81
+ And a green box has no grammar for the most common outcome of all: *we could
82
+ not read this*. Missing evidence is a fact about collection, not a fact about
83
+ the resource, and the moment it renders as a pass the whole report becomes
84
+ unreliable in a way nobody can see.
85
+
86
+ ---
87
+
88
+ ## The trust model
89
+
90
+ Every rule declares a `basis`. There are five, and the distinction between
91
+ them is the point of the project:
92
+
93
+ | basis | Meaning | Requires |
94
+ |---|---|---|
95
+ | `requirement` | The product enforces this | a source |
96
+ | `documented-limit` | A boundary the product imposes, with a number | a source, and the limit stated next to the observed value |
97
+ | `documented-guidance` | Microsoft recommends it; the alternative is permitted | a source |
98
+ | `convention` | Widely held practice, documented by nobody | a rationale |
99
+ | `opinion` | Our position, stated as ours | a rationale |
100
+
101
+ **A source never decides the type.** An opinion may cite documentation. A
102
+ convention may cite documentation. The source explains the claim; it does not
103
+ change what kind of claim it is.
104
+
105
+ **The engine never classifies.** `basis` is authored by a person and carried
106
+ into the report unchanged. The schema verifies that it is present and
107
+ justified; it never infers what it should be. That boundary is the whole
108
+ difference between automation that verifies and automation that strengthens.
109
+
110
+ Six outcomes, and `unknown` is never a pass:
111
+
112
+ ```
113
+ pass · fail · unknown · not-applicable · invalid-evidence · error
114
+ ```
115
+
116
+ `error` is the only one a rule may not author a message for: it describes the
117
+ engine, not the resource.
118
+
119
+ Full model in [docs/TRUST-MODEL.md](docs/TRUST-MODEL.md).
120
+
121
+ ---
122
+
123
+ ## Architecture
124
+
125
+ ```
126
+ collectors ─→ evidence ─→ engine ─→ results ─→ reports
127
+ ▲ ▲
128
+ schemas rules + profile
129
+ ```
130
+
131
+ Evidence flows one way. Nothing downstream writes back.
132
+
133
+ Validation happens in six layers, ordered by **scope**, and every constraint
134
+ has exactly one owner — a constraint enforced in two places will diverge the
135
+ first time one of them is corrected:
136
+
137
+ | Layer | Scope | Example |
138
+ |---|---|---|
139
+ | 1 | the file is a document | duplicate YAML keys |
140
+ | 2 | one field | `basis` is one of five |
141
+ | 3 | one file, references followed | `required` equals what the rule consumes |
142
+ | 4 | every file | duplicate ids |
143
+ | 5 | the outside world | the source URL resolves |
144
+ | 6 | a person | the source still says what the rule claims |
145
+
146
+ Layer 6 is numbered like the others on purpose. No validator can prove that a
147
+ rule represents a document correctly, and pretending otherwise would be the
148
+ exact failure the trust model exists to prevent.
149
+
150
+ Details in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and
151
+ [docs/JSON-SCHEMA-PLAN.md](docs/JSON-SCHEMA-PLAN.md).
152
+
153
+ ---
154
+
155
+ ## Install
156
+
157
+ ```bash
158
+ pip install m365-governance-as-code
159
+ ```
160
+
161
+ The rules, profiles, schemas and the collector ship inside the package, so an
162
+ installed copy works from any directory. Nothing resolves against a checkout.
163
+
164
+ Requires Python 3.11 or later. The collector additionally requires
165
+ PowerShell 7 and PnP.PowerShell, and is not needed to run anything below.
166
+
167
+ To work on the project instead of using it:
168
+
169
+ ```bash
170
+ python -m venv .venv && source .venv/bin/activate
171
+ pip install -e . && pip install -r requirements-dev.txt
172
+ ```
173
+
174
+ ### Your own rules
175
+
176
+ The packaged set is what runs when you supply nothing. A path replaces it
177
+ entirely:
178
+
179
+ ```bash
180
+ m365-governance evaluate --rules ./my-rules --evidence ./evidence
181
+ ```
182
+
183
+ **It replaces; it never merges.** Either the set that shipped with this
184
+ version, complete, or the set you supplied, complete. A rule set assembled
185
+ from both would exist only in the memory of whoever typed the command, and two
186
+ runs of the same version would stop meaning the same thing. Every report says
187
+ which of the two it used.
188
+
189
+ ---
190
+
191
+ ## Quick start
192
+
193
+ Five commands. All of them run offline, against fixtures. No tenant required.
194
+
195
+ ```bash
196
+ pip install m365-governance-as-code # 1. install
197
+ m365-governance doctor # 2. is anything broken here
198
+ m365-governance list-rules # 3. what ships with this version
199
+ m365-governance show-rule SPO-LIST-001
200
+ m365-governance evaluate --evidence <an-evidence-file.json>
201
+ ```
202
+
203
+ Example evidence ships with the package. `m365-governance doctor` prints where
204
+ it lives.
205
+
206
+ `explain unknown` is the one to run second. The project rests on six words
207
+ being different from each other, and the difference between "we could not read
208
+ this" and "this is fine" is the whole of it.
209
+
210
+ `show-rule` is the one to run first. It prints the whole claim: the basis, the
211
+ rationale behind the severity, the evidence the rule cannot decide without,
212
+ the source, and how the rule can pass while the problem survives.
213
+
214
+ ---
215
+
216
+ ## The commands
217
+
218
+ | | |
219
+ |---|---|
220
+ | `explain OUTCOME` | What `pass`, `fail`, `unknown`, `not-applicable`, `invalid-evidence` and `error` mean, and what each is not. `explain all` for the six |
221
+ | `collect SLICE` | Run a collector against a tenant and write evidence. Evaluates nothing |
222
+ | `doctor` | Python, dependencies, schemas, rules, profiles, and whether PowerShell is around. Says what it found, not only whether it liked it |
223
+ | `list-rules` | Every rule with the kind of claim it makes, strongest claim first |
224
+ | `show-rule ID` | One rule in full, including what it does not establish |
225
+ | `stats EVIDENCE` | What a collector managed to see, before anything is evaluated |
226
+ | `validate` | Every rule against the schemas and the invariants |
227
+ | `evaluate` | Rules against evidence: one document or a directory of them. Markdown, JSON or self-contained HTML |
228
+ | `report RUN.json` | Re-render a stored run in another format, without evaluating again |
229
+ | `diff BEFORE AFTER` | What moved between two assessments, and what that does not establish |
230
+
231
+ `diff` is the one a periodic audit needs:
232
+
233
+ ```
234
+ ## SPO-SITE-001
235
+
236
+ **pass → fail**
237
+
238
+ Evidence that moved:
239
+
240
+ | Path | Before | After |
241
+ |---|---|---|
242
+ | `owners.count` | 2 | 1 |
243
+ ```
244
+
245
+ It reports the rule version alongside the outcome, because a result that moved
246
+ because somebody edited the rule is not a result that moved because somebody
247
+ removed an owner. `--fail-on-regression` exits non-zero when any rule left
248
+ `pass`, including for `unknown`: losing the answer is a regression too.
249
+
250
+ ---
251
+
252
+ ## A rule
253
+
254
+ ```yaml
255
+ schema_version: "1.0"
256
+ id: SPO-SITE-001
257
+ version: "1.0"
258
+ title: A site should have at least two owners
259
+
260
+ basis:
261
+ type: convention
262
+ sources: []
263
+ rationale: >
264
+ Microsoft does not require a minimum number of owners, does not warn about
265
+ a single owner, and does not prevent a site from having one. This is our
266
+ recommendation, and an organisation may reasonably decline it.
267
+
268
+ severity:
269
+ default: medium
270
+ rationale: >
271
+ The consequence is delay, not exposure. Access decisions queue behind a
272
+ person who is not there.
273
+ configurable: true
274
+
275
+ evidence_requirements:
276
+ - path: owners.count
277
+ type: integer
278
+ required: true
279
+
280
+ condition:
281
+ operator: less-than
282
+ evidence: owners.count
283
+ value: 2
284
+
285
+ limitations:
286
+ passes_without_resolving: >
287
+ Two owners who are dormant, who have left, or who do not know they own the
288
+ site satisfy this rule completely. The count is two and nobody is able to
289
+ act.
290
+ ```
291
+
292
+ `passes_without_resolving` is mandatory. It answers one question — *how can
293
+ this rule pass while the problem survives?* — and it is a field of its own
294
+ rather than one entry in a list because it is the one that gets omitted. An
295
+ author who cannot answer it has not yet understood what the rule measures.
296
+
297
+ Rules execute no code. The condition grammar has twelve operators and no
298
+ escape hatch, because a rule that can execute stops being reviewable by
299
+ anyone who does not read the language it executes in.
300
+
301
+ ---
302
+
303
+ ## Evidence
304
+
305
+ ```json
306
+ {
307
+ "facts": {
308
+ "owners": {
309
+ "state": "observed",
310
+ "direct": [{"principal_id": "user-1", "principal_type": "user"}],
311
+ "groups": [{"principal_id": "group-1", "principal_type": "group",
312
+ "expansion": {"status": "not-attempted"}}],
313
+ "expansion_complete": false,
314
+ "minimum_count": 1
315
+ }
316
+ }
317
+ }
318
+ ```
319
+
320
+ A group owner is one principal and may be forty people. When expansion is
321
+ incomplete the collector emits a **bound**, never a guess, and the engine
322
+ reasons from the bound:
323
+
324
+ - three direct owners and one unexpanded group proves `at least 3`, so
325
+ `owners >= 2` is a **pass**, with certainty;
326
+ - one direct owner and one unexpanded group proves nothing about `owners < 2`,
327
+ so the answer is **unknown**.
328
+
329
+ Without an upper bound the engine can prove `pass` and can never prove `fail`.
330
+ That table lives in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), not in each
331
+ rule: a rule author declares the condition, and the engine decides what can be
332
+ concluded from what was collected.
333
+
334
+ `[]` means observed, and there are none. A fact that was not collected is
335
+ never an empty list.
336
+
337
+ ---
338
+
339
+ ## Output
340
+
341
+ ```markdown
342
+ ### SPO-LIST-001 v2.0
343
+
344
+ The list holds 148000 items, above Microsoft's documented limit of 100,000,
345
+ and still inherits its permissions. Inheritance can no longer be broken on
346
+ this list, so it cannot be given unique permissions later.
347
+
348
+ - Basis: **documented-limit** — a boundary the product imposes
349
+ - Severity: medium
350
+ - Evidence: `items.count` = 148000, `permissions.inheritance_broken` = false
351
+ - Source: SharePoint limits, Items in lists and libraries — checked 2026-08-05
352
+ ```
353
+
354
+ And when the answer is that there is no answer:
355
+
356
+ ```markdown
357
+ 1 rule could not be decided. That is not compliance: missing evidence is a
358
+ fact about collection, not about the resource.
359
+ ```
360
+
361
+ One example per outcome is in [examples/](examples/), every one of them
362
+ produced by a real run and checked by CI. There is no example of `error`: a
363
+ rule cannot author one, so an example of it would be an example of a bug.
364
+
365
+ ---
366
+
367
+ ## Limitations
368
+
369
+ Stated here rather than discovered:
370
+
371
+ - **Two rules.** This is a model with a working engine, not a coverage tool.
372
+ - **The collector runs delegated, never as an application.** It has been run
373
+ read-only against a real tenant and validated against PnP.PowerShell 3.3.0,
374
+ but a delegated run sees what one person sees. Every report says so on its
375
+ first line. A tenant-wide inventory needs an application identity with
376
+ `Sites.Read.All` and admin consent, and that is not implemented.
377
+ - **No group expansion.** The collector emits `minimum_count` and declares
378
+ the expansion `not-attempted`. That is honest and the engine handles it; it
379
+ is not the same as counting.
380
+ - **One profile.** A second one is created when a concrete rule needs to
381
+ differ, not before.
382
+ - **Source liveness is not checked**, and whether a source still supports the
383
+ claim can only be checked by a person.
384
+ - **The model has not been reviewed by anyone outside its authors.** Two
385
+ independent reviews are pending; see
386
+ [docs/review/RULE-REVIEW.md](docs/review/RULE-REVIEW.md). Changes already
387
+ decided but deliberately not yet applied are in
388
+ [docs/POST-REVIEW-CHANGES.md](docs/POST-REVIEW-CHANGES.md).
389
+
390
+ ---
391
+
392
+ ## Permissions
393
+
394
+ The collector is read-only and asks for the least that works:
395
+
396
+ Verified by running it, delegated, against a live tenant on 2026-08-06:
397
+
398
+ | Fact | Cmdlet | Identity used |
399
+ |---|---|---|
400
+ | Site owners | `Get-PnPSiteCollectionAdmin` | site collection administrator |
401
+ | List item count | `Get-PnPList` | read on the site |
402
+ | Permission inheritance | `Get-PnPList -Includes HasUniqueRoleAssignments` | read on the site |
403
+
404
+ `-ClientId` is required by this collector. PnP.PowerShell removed its own
405
+ multi-tenant application in [2.12.0](https://github.com/pnp/powershell/blob/dev/CHANGELOG.md),
406
+ so a connection with no client id anywhere fails before it reaches the network.
407
+ Register an Entra ID app, or use one your tenant already has.
408
+
409
+ The module accepts a
410
+ [default client id](https://pnp.github.io/powershell/articles/defaultclientid.html)
411
+ instead — `Set-PnPManagedAppId`, or `ENTRAID_APP_ID` / `ENTRAID_CLIENT_ID` /
412
+ `AZURE_CLIENT_ID`. This collector still asks for it explicitly, because
413
+ evidence has to say which identity observed it and an ambient environment
414
+ variable is one nobody can name afterwards.
415
+
416
+ An interactive sign-in produces `identity_kind: delegated`, and every report
417
+ built from it says so: that run saw what one person sees, and nothing in it
418
+ may be read as a tenant-wide statement.
419
+
420
+ A fact the identity may not read comes back as `permission-denied`, never as
421
+ absent and never as compliant.
422
+
423
+ ---
424
+
425
+ ## Roadmap
426
+
427
+ `1.0.0-beta.1` is the baseline. Milestone A closed SharePoint end to end and
428
+ does not reopen; see [docs/MILESTONE-A.md](docs/MILESTONE-A.md).
429
+
430
+ **Epic B is open**: everything one identity cannot see. Application
431
+ authentication, group expansion, importers, HTML reporting, and SARIF once the
432
+ representation of `unknown` is decided. The model is frozen for the whole of
433
+ it. See [docs/EPIC-B.md](docs/EPIC-B.md).
434
+
435
+ The table below is the direction, ordered by what would make the tool useful
436
+ to somebody else soonest rather than by what is most interesting to build.
437
+
438
+ | | |
439
+ |---|---|
440
+ | **More SharePoint rules** | Retention, site lifecycle, and the classification rules a tenant that uses labels would need. The engine is done; the work is authoring claims honestly. |
441
+ | **Coverage across a run** | Six of 53 sites refused the collector, and a report over the other 47 says 47 without saying "of 53". The envelope records coverage per document; nothing records it per run. |
442
+ | **Group expansion** | A group owner is one principal and may be forty people. The collector declares the expansion `not-attempted` and emits a lower bound; expanding it turns bounds into counts. |
443
+ | **Application authentication** | Delegated runs see what one person sees, and every count in this repository carries that clause. A tenant-wide inventory needs an app identity with `Sites.Read.All` and admin consent. |
444
+ | **Exchange, Teams and Entra collectors** | The evidence schema is service-agnostic. Each collector is new code and no new model. |
445
+ | **HTML reporting** | Markdown and JSON exist. HTML is for the reader who is not in a terminal. |
446
+ | **SARIF output** | So findings appear where code findings already appear, in a pipeline's own UI. Blocked until `unknown` has an agreed representation: SARIF has six `kind` values and this project has six outcomes, and they are not the same six. |
447
+
448
+ Open issues are the current list. This table is the direction.
449
+
450
+ **Not on the roadmap, ever:** automatic remediation, rules that execute code,
451
+ inferring `basis` from a source, or treating missing evidence as compliance.
452
+
453
+ ---
454
+
455
+ ## Contributing
456
+
457
+ Rules are the interesting contribution. A pull request that adds one is
458
+ reviewed against [docs/review/RULE-REVIEW.md](docs/review/RULE-REVIEW.md),
459
+ and the criterion is not agreement — it is disagreeing in the right place.
460
+
461
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) first; it is short. Before opening a
462
+ pull request:
463
+
464
+ ```bash
465
+ m365-governance validate
466
+ pytest
467
+ python tools/examples.py --check
468
+ ```
469
+
470
+ Also: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ·
471
+ [SECURITY.md](SECURITY.md) · [CHANGELOG.md](CHANGELOG.md)
472
+
473
+ The build fails, deliberately, when a rule is missing `basis`, when a
474
+ documented type has no source, when a convention has no rationale, when
475
+ `passes_without_resolving` is missing, when a condition reads evidence nobody
476
+ declared, when evidence is declared and never consumed, when a failure message
477
+ interpolates a value that is missing precisely when that message prints, when
478
+ a YAML key is duplicated, when two rules share an id, or when any unknown
479
+ field appears anywhere.
480
+
481
+ That last one is `additionalProperties: false` everywhere, and it is not
482
+ pedantry: `severty: high` must stop the build, not disappear.
483
+
484
+ When you change a rule, read [docs/CHANGE-POLICY.md](docs/CHANGE-POLICY.md).
485
+ The test is one sentence: *if a report produced yesterday would be interpreted
486
+ differently today, the rule version must change.*
487
+
488
+ ---
489
+
490
+ ## License
491
+
492
+ MIT. See [LICENSE](LICENSE).