@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -0,0 +1,724 @@
1
+ ---
2
+ lastUpdated: 2025-01-04T00:00:00+00:00
3
+ version: 2.0.0
4
+ reviewedBy: rbac-architecture-review
5
+ ---
6
+
7
+ # RBAC Contract
8
+
9
+ > **📋 The Definitive RBAC Contract** | [← Back to RBAC Documentation](./README.md) | [Quick Start](./quick-start.md) | [Migration Guide](./MIGRATION_GUIDE.md)
10
+
11
+ This document defines the **mandatory contract** between `pace-core` and consuming applications for RBAC (Role-Based Access Control) integration. This contract is **enforced** by ESLint rules.
12
+
13
+ **⚠️ CRITICAL**: This contract is **non-negotiable**. Violations will result in build errors.
14
+
15
+ ## Table of Contents
16
+
17
+ 1. [The Contract](#the-contract)
18
+ 2. [Architecture Boundary](#architecture-boundary)
19
+ 3. [Compliance Checklist](#compliance-checklist)
20
+ 4. [Anti-Patterns](#anti-patterns)
21
+ 5. [Enforcement](#enforcement)
22
+ 6. [enforcePermissions Configuration](#enforcepermissions-configuration)
23
+
24
+ ## The Contract
25
+
26
+ ### What Consuming Apps MUST Do
27
+
28
+ #### 1. **MUST Use pace-core RBAC Components**
29
+
30
+ **MUST use `PagePermissionGuard` for all protected pages:**
31
+
32
+ ```tsx
33
+ // ✅ CORRECT
34
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
35
+
36
+ function DashboardPage() {
37
+ return (
38
+ <PagePermissionGuard pageName="dashboard" operation="read">
39
+ <DashboardContent />
40
+ </PagePermissionGuard>
41
+ );
42
+ }
43
+ ```
44
+
45
+ **MUST use pace-core hooks for permission checks:**
46
+
47
+ ```tsx
48
+ // ✅ CORRECT
49
+ import { useCan, useResourcePermissions } from '@jmruthers/pace-core/rbac';
50
+
51
+ function MyComponent() {
52
+ const canEdit = useCan('update:users');
53
+ const permissions = useResourcePermissions('users');
54
+ // ...
55
+ }
56
+ ```
57
+
58
+ **MUST use pace-core API functions for programmatic checks:**
59
+
60
+ ```tsx
61
+ // ✅ CORRECT
62
+ import { isPermitted } from '@jmruthers/pace-core/rbac';
63
+
64
+ const hasAccess = await isPermitted({
65
+ permission: 'read:dashboard',
66
+ pageName: 'dashboard'
67
+ });
68
+ ```
69
+
70
+ #### 2. **MUST Use Standard AccessDenied Component**
71
+
72
+ **MUST use `AccessDenied` from pace-core for all access denied scenarios:**
73
+
74
+ ```tsx
75
+ // ✅ CORRECT
76
+ import { AccessDenied } from '@jmruthers/pace-core/rbac';
77
+
78
+ <PagePermissionGuard
79
+ pageName="dashboard"
80
+ operation="read"
81
+ fallback={<AccessDenied />}
82
+ >
83
+ <DashboardContent />
84
+ </PagePermissionGuard>
85
+ ```
86
+
87
+ #### 3. **MUST Configure enforcePermissions Correctly**
88
+
89
+ **For event-based apps (where pages handle their own checks):**
90
+
91
+ ```tsx
92
+ // ✅ CORRECT - Event-based app pattern
93
+ <PaceAppLayout
94
+ appName="MyApp"
95
+ enforcePermissions={false} // Pages handle checks via PagePermissionGuard
96
+ // ...
97
+ >
98
+ ```
99
+
100
+ **For organisation-based apps (where layout handles checks):**
101
+
102
+ ```tsx
103
+ // ✅ CORRECT - Organisation-based app pattern
104
+ <PaceAppLayout
105
+ appName="MyApp"
106
+ enforcePermissions={true} // Layout handles checks
107
+ defaultPermission="read"
108
+ // ...
109
+ >
110
+ ```
111
+
112
+ #### 4. **MUST Use useSecureSupabase for RBAC Table Queries**
113
+
114
+ **If you must query RBAC tables (rare), MUST use `useSecureSupabase`:**
115
+
116
+ ```tsx
117
+ // ✅ CORRECT - Using secure client
118
+ import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
119
+
120
+ function MyComponent() {
121
+ const secureSupabase = useSecureSupabase(supabase);
122
+ const { data } = await secureSupabase
123
+ .from('rbac_user_profiles') // Allowed through secure client
124
+ .select('*');
125
+ }
126
+ ```
127
+
128
+ ### What Consuming Apps MUST NOT Do
129
+
130
+ #### 1. **MUST NOT Create Wrapper Components**
131
+
132
+ **MUST NOT create wrapper components around `PagePermissionGuard`:**
133
+
134
+ ```tsx
135
+ // ❌ FORBIDDEN - Wrapper component
136
+ function EventPageGuard({ pageName, children }) {
137
+ // Event validation logic...
138
+ return (
139
+ <PagePermissionGuard pageName={pageName}>
140
+ {children}
141
+ </PagePermissionGuard>
142
+ );
143
+ }
144
+ ```
145
+
146
+ **Instead, handle validation directly in page components:**
147
+
148
+ ```tsx
149
+ // ✅ CORRECT - Direct usage
150
+ function DashboardPage() {
151
+ const { selectedEvent } = useEvents();
152
+
153
+ if (!selectedEvent) {
154
+ return <EventSelectionPrompt />;
155
+ }
156
+
157
+ return (
158
+ <PagePermissionGuard pageName="dashboard" operation="read">
159
+ <DashboardContent />
160
+ </PagePermissionGuard>
161
+ );
162
+ }
163
+ ```
164
+
165
+ #### 2. **MUST NOT Query RBAC Tables Directly**
166
+
167
+ **MUST NOT query RBAC tables without using `useSecureSupabase`:**
168
+
169
+ ```tsx
170
+ // ❌ FORBIDDEN - Direct query
171
+ const { data } = await supabase
172
+ .from('rbac_user_profiles') // ERROR: Direct RBAC table query
173
+ .select('*');
174
+ ```
175
+
176
+ **MUST NOT query these tables directly:**
177
+ - `rbac_organisation_roles`
178
+ - `rbac_event_app_roles`
179
+ - `rbac_global_roles`
180
+ - `rbac_apps`
181
+ - `rbac_app_pages`
182
+ - `rbac_page_permissions`
183
+ - `rbac_user_profiles`
184
+
185
+ #### 3. **MUST NOT Call RBAC RPC Functions Directly**
186
+
187
+ **MUST NOT call `rbac_check_permission_simplified` directly:**
188
+
189
+ ```tsx
190
+ // ❌ FORBIDDEN - Direct RPC call
191
+ const { data } = await supabase.rpc('rbac_check_permission_simplified', {
192
+ p_user_id: userId,
193
+ p_permission: 'read:dashboard'
194
+ });
195
+ ```
196
+
197
+ **Instead, use pace-core API:**
198
+
199
+ ```tsx
200
+ // ✅ CORRECT - Using pace-core API
201
+ import { isPermitted } from '@jmruthers/pace-core/rbac';
202
+ const hasAccess = await isPermitted({
203
+ permission: 'read:dashboard',
204
+ pageName: 'dashboard'
205
+ });
206
+ ```
207
+
208
+ #### 4. **MUST NOT Create Custom Access Denied Components**
209
+
210
+ **MUST NOT create custom access denied components:**
211
+
212
+ ```tsx
213
+ // ❌ FORBIDDEN - Custom component
214
+ function CustomAccessDenied() {
215
+ return <div>Access Denied</div>;
216
+ }
217
+ ```
218
+
219
+ **MUST use standard component:**
220
+
221
+ ```tsx
222
+ // ✅ CORRECT - Standard component
223
+ import { AccessDenied } from '@jmruthers/pace-core/rbac';
224
+ <AccessDenied />
225
+ ```
226
+
227
+ #### 5. **MUST NOT Bypass PagePermissionGuard**
228
+
229
+ **MUST NOT render protected content without `PagePermissionGuard`:**
230
+
231
+ ```tsx
232
+ // ❌ FORBIDDEN - No guard
233
+ function DashboardPage() {
234
+ return <DashboardContent />; // ERROR: Unprotected page
235
+ }
236
+ ```
237
+
238
+ **MUST protect all pages:**
239
+
240
+ ```tsx
241
+ // ✅ CORRECT - Protected page
242
+ function DashboardPage() {
243
+ return (
244
+ <PagePermissionGuard pageName="dashboard" operation="read">
245
+ <DashboardContent />
246
+ </PagePermissionGuard>
247
+ );
248
+ }
249
+ ```
250
+
251
+ #### 6. **MUST NOT Implement Custom Permission Logic**
252
+
253
+ **MUST NOT create custom permission checking functions:**
254
+
255
+ ```tsx
256
+ // ❌ FORBIDDEN - Custom permission logic
257
+ function checkPermission(userId, permission) {
258
+ // Custom logic...
259
+ }
260
+ ```
261
+
262
+ **MUST use pace-core functions:**
263
+
264
+ ```tsx
265
+ // ✅ CORRECT - Using pace-core
266
+ import { isPermitted } from '@jmruthers/pace-core/rbac';
267
+ const hasAccess = await isPermitted({ permission: 'read:dashboard' });
268
+ ```
269
+
270
+ #### 7. **MUST NOT Use Hardcoded Role Checks**
271
+
272
+ **MUST NOT compare roles directly:**
273
+
274
+ ```tsx
275
+ // ❌ FORBIDDEN - Hardcoded role check
276
+ if (user.role === 'admin') {
277
+ // ...
278
+ }
279
+ ```
280
+
281
+ **MUST use pace-core hooks/APIs:**
282
+
283
+ ```tsx
284
+ // ✅ CORRECT - Using pace-core
285
+ import { useAccessLevel } from '@jmruthers/pace-core/rbac';
286
+ const { accessLevel } = useAccessLevel();
287
+ if (accessLevel === 'admin') {
288
+ // ...
289
+ }
290
+ ```
291
+
292
+ #### 8. **MUST NOT Use Resource Permission String Literals**
293
+
294
+ **MUST NOT use string literals in `useResourcePermissions`:**
295
+
296
+ ```tsx
297
+ // ❌ FORBIDDEN - String literal
298
+ const { canCreate } = useResourcePermissions('journal');
299
+ ```
300
+
301
+ **MUST use RESOURCE_NAMES constant:**
302
+
303
+ ```tsx
304
+ // ✅ CORRECT - Using constant
305
+ import { RESOURCE_NAMES } from '@/config/resource-names';
306
+ const { canCreate } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
307
+ ```
308
+
309
+ #### 9. **MUST NOT Create Permission Wrapper Functions**
310
+
311
+ **MUST NOT create wrapper functions around permission checks:**
312
+
313
+ ```tsx
314
+ // ❌ FORBIDDEN - Wrapper function
315
+ const canEdit = (postId: string) => {
316
+ const hasPermission = canUpdate('journal');
317
+ const post = posts.find(p => p.id === postId);
318
+ return hasPermission && !!post;
319
+ };
320
+ ```
321
+
322
+ **MUST use pace-core hooks directly:**
323
+
324
+ ```tsx
325
+ // ✅ CORRECT - Direct usage
326
+ const { canUpdate } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
327
+ // Use canUpdate directly in components
328
+ ```
329
+
330
+ ### What Consuming Apps MAY Do
331
+
332
+ #### 1. **MAY Configure Navigation**
333
+
334
+ **MAY define navigation configuration:**
335
+
336
+ ```tsx
337
+ // ✅ ALLOWED - Navigation configuration
338
+ const navItems = [
339
+ { label: 'Dashboard', path: '/dashboard' },
340
+ { label: 'Users', path: '/users' }
341
+ ];
342
+
343
+ const routePermissions = {
344
+ '/dashboard': 'read:dashboard',
345
+ '/users': 'read:users'
346
+ };
347
+ ```
348
+
349
+ #### 2. **MAY Customize AccessDenied Props**
350
+
351
+ **MAY customize `AccessDenied` component with props:**
352
+
353
+ ```tsx
354
+ // ✅ ALLOWED - Customized AccessDenied
355
+ <AccessDenied
356
+ message="You don't have permission to view this page."
357
+ onGoBack={() => navigate('/dashboard')}
358
+ showSignOut={true}
359
+ />
360
+ ```
361
+
362
+ #### 3. **MAY Handle Event Context Validation**
363
+
364
+ **MAY validate event context in page components:**
365
+
366
+ ```tsx
367
+ // ✅ ALLOWED - Event context validation
368
+ function DashboardPage() {
369
+ const { selectedEvent } = useEvents();
370
+
371
+ if (!selectedEvent) {
372
+ return <EventSelectionPrompt />;
373
+ }
374
+
375
+ return (
376
+ <PagePermissionGuard pageName="dashboard" operation="read">
377
+ <DashboardContent />
378
+ </PagePermissionGuard>
379
+ );
380
+ }
381
+ ```
382
+
383
+ ## Architecture Boundary
384
+
385
+ ### What Lives in pace-core
386
+
387
+ **Core Permission Logic:**
388
+ - `RBACEngine.isPermitted` - Permission checking algorithm
389
+ - Database RPC calls (`rbac_check_permission_simplified`)
390
+ - Super admin detection
391
+ - Scope resolution logic
392
+ - Cache management
393
+
394
+ **Components:**
395
+ - `PagePermissionGuard` - Standard page protection (single canonical component)
396
+ - `NavigationGuard` - Navigation item protection
397
+ - `AccessDenied` - Standard access denied component
398
+
399
+ **Hooks:**
400
+ - `useCan` - Single permission check
401
+ - `usePermissions` - Permission map retrieval
402
+ - `useMultiplePermissions` - Multiple permission checks (covers any/all logic)
403
+ - `useAccessLevel` - Access level checks
404
+ - `useResourcePermissions` - Resource CRUD permissions
405
+ - `useResolvedScope` - Scope resolution
406
+ - `useSecureSupabase` - Secure Supabase client access
407
+
408
+ **API Functions:**
409
+ - `isPermitted` - Core permission check
410
+ - `getRoleContext` - Role information
411
+ - `setupRBAC` - Initialization
412
+
413
+ **Types and Utilities:**
414
+ - All RBAC types (`Permission`, `Scope`, `RBACRoleContext`)
415
+ - Security validators
416
+ - Cache utilities
417
+
418
+ ### What Lives in Consuming Apps
419
+
420
+ **Configuration:**
421
+ - Navigation configuration (`navItems`, `routePermissions`, `pageIdMapping`)
422
+ - App-specific page names and constants
423
+
424
+ **UI Integration:**
425
+ - Permission-aware UI rendering (using pace-core hooks)
426
+ - Custom styling of standard `AccessDenied` component (via props)
427
+ - Event context validation (if needed)
428
+
429
+ ## Compliance Checklist
430
+
431
+ Before deploying your application, verify:
432
+
433
+ - [ ] All pages are wrapped with `PagePermissionGuard`
434
+ - [ ] No direct queries to RBAC tables (except through `useSecureSupabase`)
435
+ - [ ] No direct RPC calls to `rbac_check_permission_simplified`
436
+ - [ ] No wrapper components around `PagePermissionGuard`
437
+ - [ ] No custom access denied components
438
+ - [ ] No hardcoded role checks (use `useAccessLevel` or `getRoleContext`)
439
+ - [ ] No custom permission utility functions
440
+ - [ ] No resource permission string literals (use `RESOURCE_NAMES` constants)
441
+ - [ ] No permission wrapper functions (use pace-core hooks directly)
442
+ - [ ] `enforcePermissions` configured correctly for your app type
443
+ - [ ] All access denied scenarios use `AccessDenied` from pace-core
444
+ - [ ] ESLint rules pass without errors
445
+
446
+ ## Anti-Patterns
447
+
448
+ ### Anti-Pattern 1: Wrapper Components
449
+
450
+ ```tsx
451
+ // ❌ ANTI-PATTERN
452
+ function EventPageGuard({ pageName, children }) {
453
+ const { selectedEvent } = useEvents();
454
+ if (!selectedEvent) return <EventSelectionPrompt />;
455
+ return <PagePermissionGuard pageName={pageName}>{children}</PagePermissionGuard>;
456
+ }
457
+ ```
458
+
459
+ **Why it's wrong:**
460
+ - Decreases maintainability
461
+ - Adds unnecessary abstraction
462
+ - Duplicates logic that should be in pages
463
+
464
+ **Correct pattern:**
465
+
466
+ ```tsx
467
+ // ✅ CORRECT
468
+ function DashboardPage() {
469
+ const { selectedEvent } = useEvents();
470
+ if (!selectedEvent) return <EventSelectionPrompt />;
471
+ return (
472
+ <PagePermissionGuard pageName="dashboard" operation="read">
473
+ <DashboardContent />
474
+ </PagePermissionGuard>
475
+ );
476
+ }
477
+ ```
478
+
479
+ ### Anti-Pattern 2: Direct Database Queries
480
+
481
+ ```tsx
482
+ // ❌ ANTI-PATTERN
483
+ const { data } = await supabase
484
+ .from('rbac_user_profiles')
485
+ .select('*');
486
+ ```
487
+
488
+ **Why it's wrong:**
489
+ - Bypasses security controls
490
+ - Violates architecture boundary
491
+ - Not future-proof
492
+
493
+ **Correct pattern:**
494
+
495
+ ```tsx
496
+ // ✅ CORRECT
497
+ import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
498
+ const secureSupabase = useSecureSupabase(supabase);
499
+ const { data } = await secureSupabase
500
+ .from('rbac_user_profiles')
501
+ .select('*');
502
+ ```
503
+
504
+ ### Anti-Pattern 3: Custom Access Denied
505
+
506
+ ```tsx
507
+ // ❌ ANTI-PATTERN
508
+ function CustomAccessDenied() {
509
+ return <div>Access Denied</div>;
510
+ }
511
+ ```
512
+
513
+ **Why it's wrong:**
514
+ - Inconsistent UX across apps
515
+ - Duplicates standard functionality
516
+ - Not maintainable
517
+
518
+ **Correct pattern:**
519
+
520
+ ```tsx
521
+ // ✅ CORRECT
522
+ import { AccessDenied } from '@jmruthers/pace-core/rbac';
523
+ <AccessDenied />
524
+ ```
525
+
526
+ ### Anti-Pattern 4: Hardcoded Role Checks
527
+
528
+ ```tsx
529
+ // ❌ ANTI-PATTERN
530
+ if (user.role === 'admin') {
531
+ // ...
532
+ }
533
+ ```
534
+
535
+ **Why it's wrong:**
536
+ - Bypasses RBAC system
537
+ - Not maintainable (role names can change)
538
+ - Doesn't respect permission hierarchy
539
+
540
+ **Correct pattern:**
541
+
542
+ ```tsx
543
+ // ✅ CORRECT
544
+ import { useAccessLevel } from '@jmruthers/pace-core/rbac';
545
+ const { accessLevel } = useAccessLevel();
546
+ if (accessLevel === 'admin') {
547
+ // ...
548
+ }
549
+ ```
550
+
551
+ ### Anti-Pattern 5: Custom Permission Utilities
552
+
553
+ ```tsx
554
+ // ❌ ANTI-PATTERN
555
+ function checkPermission(userId, permission) {
556
+ // Custom logic...
557
+ }
558
+ ```
559
+
560
+ **Why it's wrong:**
561
+ - Duplicates pace-core functionality
562
+ - Bypasses centralized RBAC logic
563
+ - Not future-proof
564
+
565
+ **Correct pattern:**
566
+
567
+ ```tsx
568
+ // ✅ CORRECT
569
+ import { isPermitted } from '@jmruthers/pace-core/rbac';
570
+ const hasAccess = await isPermitted({ permission: 'read:dashboard' });
571
+ ```
572
+
573
+ ### Anti-Pattern 6: Resource Permission String Literals
574
+
575
+ ```tsx
576
+ // ❌ ANTI-PATTERN
577
+ const { canCreate } = useResourcePermissions('journal');
578
+ ```
579
+
580
+ **Why it's wrong:**
581
+ - Not type-safe (typos possible)
582
+ - No compile-time validation
583
+ - Maintenance burden
584
+
585
+ **Correct pattern:**
586
+
587
+ ```tsx
588
+ // ✅ CORRECT
589
+ import { RESOURCE_NAMES } from '@/config/resource-names';
590
+ const { canCreate } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
591
+ ```
592
+
593
+ ### Anti-Pattern 7: Permission Wrapper Functions
594
+
595
+ ```tsx
596
+ // ❌ ANTI-PATTERN
597
+ const canEdit = (postId: string) => {
598
+ const hasPermission = canUpdate('journal');
599
+ const post = posts.find(p => p.id === postId);
600
+ return hasPermission && !!post;
601
+ };
602
+ ```
603
+
604
+ **Why it's wrong:**
605
+ - Unnecessary abstraction layer
606
+ - Encourages bypassing direct pace-core usage
607
+ - Creates maintenance burden
608
+
609
+ **Correct pattern:**
610
+
611
+ ```tsx
612
+ // ✅ CORRECT
613
+ const { canUpdate } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
614
+ // Use canUpdate directly in components
615
+ ```
616
+
617
+ ## Enforcement
618
+
619
+ ### ESLint Rules
620
+
621
+ The following ESLint rules enforce this contract (all as **ERROR** severity):
622
+
623
+ 1. **`no-direct-rbac-rpc`** - Detects direct calls to `rbac_check_permission_simplified`
624
+ 2. **`no-direct-rbac-tables`** - Detects direct queries to RBAC tables
625
+ 3. **`no-bypass-page-guard`** - Detects routes without `PagePermissionGuard`
626
+ 4. **`no-custom-access-denied`** - Detects custom access denied components
627
+ 5. **`no-hardcoded-role-checks`** - Detects hardcoded role comparisons
628
+ 6. **`no-custom-permission-utilities`** - Detects custom permission utility functions
629
+ 7. **`no-resource-permission-string-literals`** - Detects string literals in `useResourcePermissions` calls
630
+ 8. **`no-permission-wrapper-functions`** - Detects wrapper functions around pace-core permission hooks
631
+
632
+ ### Runtime Compliance
633
+
634
+ Use runtime compliance checking in development:
635
+
636
+ ```tsx
637
+ import { checkRuntimeCompliance } from '@jmruthers/pace-core/rbac/compliance';
638
+
639
+ // In development mode
640
+ if (import.meta.env.DEV) {
641
+ checkRuntimeCompliance();
642
+ }
643
+ ```
644
+
645
+ ## enforcePermissions Configuration
646
+
647
+ ### When to Use `enforcePermissions={true}`
648
+
649
+ **Use for organisation-based apps** where the layout handles permission checks:
650
+
651
+ ```tsx
652
+ <PaceAppLayout
653
+ appName="MyApp"
654
+ enforcePermissions={true} // Layout validates all routes
655
+ defaultPermission="read"
656
+ routePermissions={{
657
+ '/dashboard': 'read:dashboard',
658
+ '/users': 'read:users'
659
+ }}
660
+ >
661
+ ```
662
+
663
+ **Benefits:**
664
+ - Automatic route protection
665
+ - Safety net for forgotten permission checks
666
+ - Centralized permission configuration
667
+
668
+ ### When to Use `enforcePermissions={false}`
669
+
670
+ **Use for event-based apps** where pages handle their own checks:
671
+
672
+ ```tsx
673
+ <PaceAppLayout
674
+ appName="MyApp"
675
+ enforcePermissions={false} // Pages handle checks via PagePermissionGuard
676
+ showEvents={true}
677
+ >
678
+ {/* Pages use PagePermissionGuard directly */}
679
+ </PaceAppLayout>
680
+ ```
681
+
682
+ **Why `false` for event-based apps:**
683
+ - Pages need event context validation before permission checks
684
+ - `PagePermissionGuard` handles both event validation and permission checks
685
+ - Avoids redundant checks and timing issues
686
+
687
+ **Pattern:**
688
+
689
+ ```tsx
690
+ // Each page handles its own checks
691
+ function DashboardPage() {
692
+ const { selectedEvent } = useEvents();
693
+ if (!selectedEvent) return <EventSelectionPrompt />;
694
+
695
+ return (
696
+ <PagePermissionGuard pageName="dashboard" operation="read">
697
+ <DashboardContent />
698
+ </PagePermissionGuard>
699
+ );
700
+ }
701
+ ```
702
+
703
+ ## Related Documentation
704
+
705
+ - [RBAC README](./README.md) - Overview and quick start
706
+ - [Quick Start Guide](./quick-start.md) - Step-by-step setup
707
+ - [Event-Based Apps](./event-based-apps.md) - Event-based app patterns
708
+ - [Permission Enforcement](../implementation-guides/permission-enforcement.md) - Detailed enforcement patterns
709
+ - [Migration Guide](./MIGRATION_GUIDE.md) - Migrating to this contract
710
+
711
+ ## Support
712
+
713
+ If you have questions about this contract or need clarification:
714
+
715
+ 1. Review the [Troubleshooting Guide](./troubleshooting.md)
716
+ 2. Check [Examples](./examples.md) for common patterns
717
+ 3. Review [API Reference](./api-reference.md) for detailed API documentation
718
+
719
+ ---
720
+
721
+ **Last Updated**: 2025-01-04
722
+ **Version**: 2.0.0
723
+ **Status**: Enforced
724
+