spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,64 +1,38 @@
1
- // ─── CHUNK_START: PERMISSIONS_OVERVIEW ──────────────────────────────────────────────
2
- /**
3
- * @module permissions
4
- * @audience both
5
- * @layer shared-core
6
- * @stability stable
7
- *
8
- * Single source of truth for all authorization in Spine. Exports one singleton —
9
- * `PermissionEngine` — that routes every access check to one of three permission
10
- * surfaces based on the table being accessed:
11
- *
12
- * First surface — runtime data (items, accounts, people, threads, messages…)
13
- * Schema-driven: permissions are encoded in `design_schema.record_permissions`
14
- * and `design_schema.fields[x].permissions` stamped on the record at creation.
15
- *
16
- * Second surface — config objects (apps, pipelines, triggers, roles, types…)
17
- * Role-driven: system_admin full access, machine read, others denied.
18
- *
19
- * Third surface — system metadata (logs, pipeline_executions, link_types…)
20
- * Ownership-driven: users read their own, system_admin sees all.
21
- *
22
- * INVARIANT: system_admin bypasses ALL surface checks. No other bypass exists.
23
- * INVARIANT: missing or empty `design_schema` on a first-surface record is an
24
- * explicit deny — not a free pass. RLS controls row access; design_schema
25
- * controls what the principal can do with the row.
26
- * INVARIANT: never import or instantiate `_PermissionEngineInternal` directly.
27
- * Always import the `PermissionEngine` singleton or the named legacy exports.
28
- *
29
- * @seeAlso db.ts (adminDb used for schema and person lookups)
30
- * @seeAlso principal.ts (Principal interface, isSystemAdmin, getPrincipalDb)
31
- * @seeAlso middleware.ts (CoreContext shape, ctx.db, ctx.principal)
32
- * @seeAlso schema-utils.ts (formatFieldData, sanitizeFieldData called during sanitization)
33
- * @seeAlso index.ts (stable export surface for custom code)
34
- */
35
-
36
- // ─── CHUNK_END: PERMISSIONS_OVERVIEW ────────────────────────────────────────────────
37
-
38
- // ─── CHUNK_START: PERMISSIONS_TYPES ──────────────────────────────────────────────
1
+ // CIL[BEGIN chunk_id="permissions_moduleheader_1784066609" type="module_header" identity="module_header" parent="none"]
2
+ // CIL[DOC-MACRO purpose="Permission system for data access control"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Manage record and field-level permissions"]
4
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\"\\\"]"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="permission utilities" errors="none" side_effects="access control"]
6
+ // CIL[DOC-MICRO logic_summary="Defines PermissionEngine and permission resolution logic" notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="developer"]
10
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/IntegrationsPage.tsx, \."]
11
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
12
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
13
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
14
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
15
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
16
+ // CIL[END chunk_id="permissions_moduleheader_1784066609"]
17
+ // CIL[BEGIN chunk_id="permissions_types_1784066609" type="module_header" identity="types" parent="none"]
18
+ // CIL[DOC-MACRO purpose="Permission types and interfaces"]
19
+ // CIL[DOC-RESPONSIBILITY responsibility="Define permission data structures"]
20
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
21
+ // CIL[DOC-CONTRACT inputs="none" outputs="type definitions" errors="none" side_effects="type definitions"]
22
+ // CIL[DOC-MICRO logic_summary="Defines PermissionResult interface and imports" notable_decisions="[]"]
23
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
24
+ // CIL[DOC-STABILITY stability="stable"]
25
+ // CIL[DOC-AUDIENCE audience="developer"]
26
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/functions/_shared/principal.ts, .framework/src/components/ui/dialog.tsx, \."]
27
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
28
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
29
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
30
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
31
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
39
32
  import { adminDb } from './db'
40
33
  import { Principal } from './principal'
41
34
  import { CoreContext } from './middleware'
42
35
 
43
- /**
44
- * Result of a permission resolution for a principal + record + action combination.
45
- *
46
- * Returned by `resolveFirstSurfacePermissions`. Captures both record-level CRUD
47
- * flags and per-field read/write flags derived from `design_schema`.
48
- *
49
- * All flags default to `false` on any error or missing schema — never assume
50
- * a missing flag means "allowed".
51
- *
52
- * @inputSpec none — this is a pure output type
53
- * @outputSpec canCreate: boolean — principal may create records of this type
54
- * @outputSpec canRead: boolean — principal may read this record
55
- * @outputSpec canUpdate: boolean — principal may update this record
56
- * @outputSpec canDelete: boolean — principal may delete this record
57
- * @outputSpec fieldPermissions: Record<fieldName, {read, write}> — per-field flags
58
- * derived from design_schema.fields[x].permissions merged across all roles
59
- * @calledBy resolveFirstSurfacePermissions (producer), sanitizeFirstSurfaceRecordData,
60
- * validateFirstSurfaceUpdatePermissions, canAccessFirstSurfaceRecord (consumers)
61
- */
62
36
  export interface PermissionResult {
63
37
  canCreate: boolean
64
38
  canRead: boolean
@@ -68,34 +42,39 @@ export interface PermissionResult {
68
42
  }
69
43
 
70
44
  type RequestContext = CoreContext
71
- // ─── CHUNK_END: PERMISSIONS_TYPES ────────────────────────────────────────────────
72
-
73
- // ─── CHUNK_START: PERMISSIONS_ENGINE_CLASS ──────────────────────────────────────────────
74
- /**
75
- * The single permission engine for all authorization in Spine.
76
- *
77
- * Instantiated once as a module-level singleton (`PermissionEngine`). Routes
78
- * every check through one of three surfaces based on table classification.
79
- * All public methods are async and never throw — on any internal error they
80
- * fall back to a deny result to avoid accidental permission grants.
81
- *
82
- * Do not instantiate directly. Import `PermissionEngine` or use the named
83
- * legacy exports (`sanitizeRecordData`, `validateUpdatePermissions`, etc.).
84
- *
85
- * @audience both
86
- * @stability stable
87
- * @calledBy All 19 API handlers via sanitizeRecordData / validateUpdatePermissions
88
- * @calledBy admin-data.ts (primary consumer for runtime data)
89
- * @testUnit tests/unit/permissions.test.ts
90
- * @testIntegration tests/integration/isolation.test.ts, admin-data-accounts.test.ts
91
- */
92
- // ─── CHUNK_END: PERMISSIONS_ENGINE_CLASS ────────────────────────────────────────────────
93
-
94
- // ─── CHUNK_START: PERMISSIONS_SURFACE_CLASSIFICATION ──────────────────────────────────────────────
45
+ // CIL[END chunk_id="permissions_types_1784066609"]
46
+ // CIL[BEGIN chunk_id="permissions_engineclass_1784066609" type="class" identity="_PermissionEngineInternal" parent="none"]
47
+ // CIL[DOC-MACRO purpose="Permission engine internal class placeholder"]
48
+ // CIL[DOC-RESPONSIBILITY responsibility="Define permission engine structure"]
49
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[\".framework/functions/_shared/principal.ts\",\"\\\"]"]
50
+ // CIL[DOC-CONTRACT inputs="none" outputs="class definition" errors="none" side_effects="none"]
51
+ // CIL[DOC-MICRO logic_summary="Empty class definition for permission engine" notable_decisions="[]"]
52
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
53
+ // CIL[DOC-STABILITY stability="stable"]
54
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, \."]
55
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
56
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
57
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
58
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
59
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
60
+ // CIL[END chunk_id="permissions_engineclass_1784066609"]
61
+ // CIL[BEGIN chunk_id="permissions_classification_1784066609" type="class" identity="_PermissionEngineInternal" parent="permissions_engineclass_1784066609"]
62
+ // CIL[DOC-MACRO purpose="Permission engine implementation"]
63
+ // CIL[DOC-RESPONSIBILITY responsibility="Implement permission resolution logic"]
64
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[\".framework/functions/_shared/principal.ts\",\"\\\"]"]
65
+ // CIL[DOC-CONTRACT inputs="none" outputs="permission methods" errors="none" side_effects="permission evaluation"]
66
+ // CIL[DOC-MICRO logic_summary="Implements singleton permission engine with surface detection" notable_decisions="[]"]
67
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
68
+ // CIL[DOC-STABILITY stability="stable"]
69
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, \."]
70
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
71
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
72
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
73
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
74
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
95
75
  class _PermissionEngineInternal {
96
76
  private static instance: _PermissionEngineInternal
97
77
 
98
- // Surface classification tables
99
78
  private readonly SECOND_SURFACE_TABLES = new Set([
100
79
  'apps', 'app', 'pipelines', 'triggers', 'ai_agents', 'embeddings',
101
80
  'timers', 'integrations', 'roles', 'types', 'prompt_configs'
@@ -108,22 +87,6 @@ class _PermissionEngineInternal {
108
87
 
109
88
  private constructor() {}
110
89
 
111
- /**
112
- * Classifies a table name into one of Spine's three permission surfaces.
113
- *
114
- * Surface membership is determined by static set membership — if a table is
115
- * not in SECOND_SURFACE_TABLES or THIRD_SURFACE_TABLES, it defaults to first.
116
- * This is intentionally conservative: unknown tables get the most restrictive
117
- * surface (first), which requires a valid design_schema to grant any access.
118
- *
119
- * @param tableName - Table name string (e.g. 'items', 'pipelines', 'logs')
120
- * @returns 'first' | 'second' | 'third' — surface classification
121
- * @throws never
122
- * @inputSpec tableName: string — any string; unknown names → 'first'
123
- * @outputSpec 'first' | 'second' | 'third'
124
- * @sideEffects none
125
- * @calledBy canAccessRecord, sanitizeRecordData, validateUpdatePermissions
126
- */
127
90
  private detectSurface(tableName: string): 'first' | 'second' | 'third' {
128
91
  if (this.SECOND_SURFACE_TABLES.has(tableName)) {
129
92
  return 'second'
@@ -134,109 +97,47 @@ class _PermissionEngineInternal {
134
97
  return 'first'
135
98
  }
136
99
 
137
- /**
138
- * Extracts a table/type name from a record to use for surface classification.
139
- *
140
- * Tries multiple fields in priority order: `record.table_name` (explicitly
141
- * set by some handlers), `record.type`, `record.item_type`, then the
142
- * `typeSlug` param. Falls back to `'unknown'` which routes to first surface.
143
- *
144
- * @param record - The record object being classified
145
- * @param typeSlug - Optional caller-provided type slug (used as last resort)
146
- * @returns string — table name used to classify the permission surface
147
- * @throws never
148
- * @inputSpec record: object — any record; missing fields are safely ignored
149
- * @inputSpec typeSlug: string | undefined — optional fallback
150
- * @outputSpec string — one of the known table names, or 'unknown'
151
- * @sideEffects none
152
- * @calledBy canAccessRecord, sanitizeRecordData, validateUpdatePermissions
153
- */
154
100
  private extractTableName(record: any, typeSlug?: string): string {
155
- // Try to get table name from record context
156
101
  if (record?.table_name) {
157
102
  return record.table_name
158
103
  }
159
104
 
160
- // Try to get from type field
161
105
  if (record?.type) {
162
106
  return record.type
163
107
  }
164
108
 
165
- // Try to get from item_type field
166
109
  if (record?.item_type) {
167
110
  return record.item_type
168
111
  }
169
112
 
170
- // Use provided typeSlug
171
113
  if (typeSlug) {
172
114
  return typeSlug
173
115
  }
174
116
 
175
- // Default to unknown (will be treated as first surface)
176
117
  return 'unknown'
177
118
  }
178
119
 
179
- /**
180
- * Returns the singleton instance. Called once at module load time to
181
- * initialise `PermissionEngine`. Not for direct use outside this file.
182
- *
183
- * @returns _PermissionEngineInternal — the single shared instance
184
- * @throws never
185
- * @sideEffects creates instance on first call (subsequent calls return cached)
186
- * @calledBy module initialisation (bottom of this file)
187
- */
188
120
  static getInstance(): _PermissionEngineInternal {
189
121
  if (!_PermissionEngineInternal.instance) {
190
122
  _PermissionEngineInternal.instance = new _PermissionEngineInternal()
191
123
  }
192
124
  return _PermissionEngineInternal.instance
193
125
  }
194
-
195
- // ─── CHUNK_END: PERMISSIONS_SURFACE_CLASSIFICATION ────────────────────────────────────────────────
196
-
197
- // ─── CHUNK_START: PERMISSIONS_FIRST_SURFACE_RESOLVE ──────────────────────────────────────────────
198
-
199
- /**
200
- * Resolves record-level and field-level permissions for a human principal
201
- * acting on a first-surface (runtime data) record.
202
- *
203
- * Resolution steps:
204
- * 1. Load `design_schema` from the type record if not pre-stamped on the record
205
- * 2. Look up the person's role via `people.role_id` FK (single DB query)
206
- * 3. Evaluate `design_schema.record_permissions[role]` array for CRUD flags
207
- * 4. Evaluate `design_schema.fields[x].permissions[role]` for field flags
208
- * 5. Apply `'all'` wildcard role key if present (grants to all authenticated)
209
- * 6. For fields with no explicit permission, inherit from record-level flags
210
- *
211
- * Returns all-deny `PermissionResult` on any error — never throws.
212
- *
213
- * @param personId - UUID of the person making the request (from principal.id)
214
- * @param accountId - UUID of the account context for the operation
215
- * @param typeSlug - Slug of the type to look up design_schema if not pre-stamped
216
- * @param _action - CRUD action (currently used for context; merge logic is role-based)
217
- * @param designSchema - Pre-loaded design_schema object (skips DB lookup if provided)
218
- *
219
- * @inputSpec personId: string — valid UUID, must exist in people table with is_active=true
220
- * @inputSpec accountId: string — valid UUID of accessible account
221
- * @inputSpec typeSlug: string — slug of a type in the types table with is_active=true
222
- * @inputSpec designSchema: object | undefined — if provided, must have record_permissions
223
- * @outputSpec PermissionResult — all flags false on error/missing schema
224
- * @throws never — catches all errors, returns defaultResult
225
- * @sideEffects DB read: types table (if schema not pre-stamped), people table (role lookup)
226
- * @calledBy canAccessFirstSurfaceRecord, sanitizeFirstSurfaceRecordData,
227
- * validateFirstSurfaceUpdatePermissions
228
- * @calls adminDb.from('types'), adminDb.from('people')
229
- * @testUnit tests/unit/permissions.test.ts — 'resolveFirstSurfacePermissions' describe block
230
- *
231
- * @example Import usage (v2-custom/)
232
- * ```ts
233
- * import { PermissionEngine } from '../_shared/index'
234
- * const perms = await PermissionEngine.resolveFirstSurfacePermissions(
235
- * ctx.principal.id, ctx.accountId, 'ticket', 'read'
236
- * )
237
- * if (!perms.canRead) return { error: 'Forbidden' }
238
- * ```
239
- */
126
+ // CIL[END chunk_id="permissions_classification_1784066609"]
127
+ // CIL[BEGIN chunk_id="permissions_resolvefirstsurface_1784066609" type="function" identity="resolveFirstSurfacePermissions" parent="permissions_classification_1784066609"]
128
+ // CIL[DOC-MACRO purpose="Resolve first surface permissions"]
129
+ // CIL[DOC-RESPONSIBILITY responsibility="Evaluate record and field permissions for runtime data"]
130
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[\".framework/migrations/025_retype_threads_messages.sql\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
131
+ // CIL[DOC-CONTRACT inputs="none" outputs="PermissionResult" errors="database errors" side_effects="database reads"]
132
+ // CIL[DOC-MICRO logic_summary="Resolves permissions based on role and design schema" notable_decisions="[]"]
133
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_dependencies"]
134
+ // CIL[DOC-STABILITY stability="stable"]
135
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/025_retype_threads_messages.sql, .framework/functions/_shared/principal.ts, \."]
136
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
137
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
138
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
139
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
140
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
240
141
  async resolveFirstSurfacePermissions(
241
142
  personId: string,
242
143
  accountId: string,
@@ -338,30 +239,21 @@ class _PermissionEngineInternal {
338
239
  return defaultResult
339
240
  }
340
241
  }
341
-
342
- // ─── CHUNK_END: PERMISSIONS_FIRST_SURFACE_RESOLVE ────────────────────────────────────────────────
343
-
344
- // ─── CHUNK_START: PERMISSIONS_SECOND_SURFACE_CONFIG_OBJECTS ──────────────────────────────────────────────
345
-
346
- /**
347
- * Checks whether the principal in `ctx` may perform `action` on a second-surface
348
- * config object (apps, pipelines, triggers, roles, types, etc.).
349
- *
350
- * Rules:
351
- * - system_admin: full access to all actions
352
- * - machine principal: read-only
353
- * - all others: denied
354
- *
355
- * @param ctx - Request context containing principal
356
- * @param action - CRUD action being attempted
357
- * @returns boolean — true if access is allowed
358
- * @throws never
359
- * @inputSpec ctx.principal: Principal — must be resolved (not anonymous)
360
- * @inputSpec action: 'create' | 'read' | 'update' | 'delete'
361
- * @outputSpec boolean — true = allowed, false = denied
362
- * @sideEffects none
363
- * @calledBy canAccessRecord (surface='second'), validateConfigObjectPermissions
364
- */
242
+ // CIL[END chunk_id="permissions_resolvefirstsurface_1784066609"]
243
+ // CIL[BEGIN chunk_id="permissions_canaccessconfig_1784066609" type="function" identity="canAccessConfigObject" parent="permissions_classification_1784066609"]
244
+ // CIL[DOC-MACRO purpose="Export permission engine instance and methods"]
245
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide public API for permission system"]
246
+ // CIL[DOC-CONTEXT module="_shared/permissions" layer="infrastructure" related_files="[]"]
247
+ // CIL[DOC-CONTRACT inputs="none" outputs="exports" errors="none" side_effects="module exports"]
248
+ // CIL[DOC-MICRO logic_summary="Exports PermissionEngine singleton and bound methods" notable_decisions="[]"]
249
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
250
+ // CIL[DOC-STABILITY stability="stable"]
251
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: 0. Direct calls: 0. Related files: none."]
252
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
253
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
254
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
255
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
256
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
365
257
  private canAccessConfigObject(ctx: RequestContext, action: 'create' | 'read' | 'update' | 'delete'): boolean {
366
258
  // System admin has full access
367
259
  if (this.isSystemAdmin(ctx)) {
@@ -377,24 +269,21 @@ class _PermissionEngineInternal {
377
269
  return false
378
270
  }
379
271
 
380
- /**
381
- * Strips fields from a second-surface config record based on the principal's access.
382
- *
383
- * system_admin and machine principals receive the full record. All others
384
- * receive only `{ id, created_at, updated_at }`. This is intentionally strict
385
- * — config objects contain sensitive pipeline logic, schema definitions, and
386
- * integration credentials that must not leak to end users.
387
- *
388
- * @param ctx - Request context
389
- * @param record - The config record to sanitize
390
- * @returns Sanitized record — full record or minimal stub
391
- * @throws never
392
- * @inputSpec ctx.principal: Principal — resolved principal
393
- * @inputSpec record: object — must have id, created_at, updated_at at minimum
394
- * @outputSpec object — full record for admin/machine, { id, created_at, updated_at } for others
395
- * @sideEffects none
396
- * @calledBy sanitizeRecordData (surface='second')
397
- */
272
+ // CIL[END chunk_id="permissions_canaccessconfig_1784066609"]
273
+ // CIL[BEGIN chunk_id="permissions_sanitizeconfig_1784066609" type="function" identity="sanitizeConfigObject" parent="permissions_classification_1784066609"]
274
+ // CIL[DOC-MACRO purpose="sanitizeConfigObject"]
275
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle sanitizeConfigObject"]
276
+ // CIL[DOC-CONTEXT module="sanitizeConfigObject" layer="unknown" related_files="[]"]
277
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
278
+ // CIL[DOC-MICRO logic_summary="Implements sanitizeConfigObject" notable_decisions="[]"]
279
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
280
+ // CIL[DOC-STABILITY stability="stable"]
281
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
282
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
283
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
284
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
285
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
286
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
398
287
  private sanitizeConfigObject(ctx: RequestContext, record: any): any {
399
288
  // Debug logging
400
289
  console.log('sanitizeConfigObject called with record:', {
@@ -448,22 +337,21 @@ class _PermissionEngineInternal {
448
337
  }
449
338
  }
450
339
 
451
- /**
452
- * Validates whether the principal may perform `action` on a second-surface record.
453
- * Thin wrapper around `canAccessConfigObject` that returns a typed result object
454
- * suitable for returning directly from handler validation checks.
455
- *
456
- * @param ctx - Request context
457
- * @param action - CRUD action being validated
458
- * @returns { valid: boolean, error?: string }
459
- * @throws never
460
- * @inputSpec ctx.principal: Principal — resolved principal
461
- * @inputSpec action: 'create' | 'read' | 'update' | 'delete'
462
- * @outputSpec valid: boolean — true if action is permitted
463
- * @outputSpec error: string | undefined — human-readable denial reason if !valid
464
- * @sideEffects none
465
- * @calledBy validateUpdatePermissions (surface='second')
466
- */
340
+ // CIL[END chunk_id="permissions_sanitizeconfig_1784066609"]
341
+ // CIL[BEGIN chunk_id="permissions_validateconfig_1784066609" type="function" identity="validateConfigObjectPermissions" parent="permissions_classification_1784066609"]
342
+ // CIL[DOC-MACRO purpose="validateConfigObjectPermissions"]
343
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle validateConfigObjectPermissions"]
344
+ // CIL[DOC-CONTEXT module="validateConfigObjectPermissions" layer="unknown" related_files="[]"]
345
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
346
+ // CIL[DOC-MICRO logic_summary="Implements validateConfigObjectPermissions" notable_decisions="[]"]
347
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
348
+ // CIL[DOC-STABILITY stability="stable"]
349
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
350
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
351
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
352
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
353
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
354
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
467
355
  private validateConfigObjectPermissions(ctx: RequestContext, action: 'create' | 'read' | 'update' | 'delete'): { valid: boolean; error?: string } {
468
356
  if (this.canAccessConfigObject(ctx, action)) {
469
357
  return { valid: true }
@@ -472,36 +360,21 @@ class _PermissionEngineInternal {
472
360
  return { valid: false, error: 'Insufficient permissions for this operation' }
473
361
  }
474
362
 
475
- // ─── CHUNK_END: PERMISSIONS_SECOND_SURFACE_CONFIG_OBJECTS ────────────────────────────────────────────────
476
-
477
- // ─── CHUNK_START: PERMISSIONS_THIRD_SURFACE_SYSTEM_METADATA ──────────────────────────────────────────────
478
-
479
- /**
480
- * Checks whether the principal may access a third-surface system metadata record
481
- * (logs, pipeline_executions, trigger_executions, link_types, links).
482
- *
483
- * Rules:
484
- * - system_admin: full access
485
- * - machine principal: full access
486
- * - human principal (read only):
487
- * - owns the record (created_by === principal.id), OR
488
- * - record is scoped to the principal's account (account_id === ctx.accountId), OR
489
- * - record references the principal directly (person_id === principal.id)
490
- * - human principal (create/update/delete): always denied
491
- *
492
- * @param ctx - Request context
493
- * @param record - The system metadata record being accessed
494
- * @param action - CRUD action being attempted
495
- * @returns boolean — true if access is allowed
496
- * @throws never
497
- * @inputSpec ctx.principal: Principal — resolved principal
498
- * @inputSpec record: object — must have at least one of: created_by, account_id, person_id
499
- * @inputSpec action: 'create' | 'read' | 'update' | 'delete'
500
- * @outputSpec boolean
501
- * @sideEffects none
502
- * @calledBy canAccessRecord (surface='third'), sanitizeSystemMetadata,
503
- * validateSystemMetadataPermissions
504
- */
363
+ // CIL[END chunk_id="permissions_validateconfig_1784066609"]
364
+ // CIL[BEGIN chunk_id="permissions_canaccesssystem_1784066609" type="function" identity="canAccessSystemMetadata" parent="permissions_classification_1784066609"]
365
+ // CIL[DOC-MACRO purpose="canAccessSystemMetadata"]
366
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle canAccessSystemMetadata"]
367
+ // CIL[DOC-CONTEXT module="canAccessSystemMetadata" layer="unknown" related_files="[]"]
368
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
369
+ // CIL[DOC-MICRO logic_summary="Implements canAccessSystemMetadata" notable_decisions="[]"]
370
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
371
+ // CIL[DOC-STABILITY stability="stable"]
372
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
373
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
374
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
375
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
376
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
377
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
505
378
  private canAccessSystemMetadata(ctx: RequestContext, record: any, action: 'create' | 'read' | 'update' | 'delete'): boolean {
506
379
  // System admin has full access
507
380
  if (this.isSystemAdmin(ctx)) {
@@ -535,22 +408,21 @@ class _PermissionEngineInternal {
535
408
  return false
536
409
  }
537
410
 
538
- /**
539
- * Strips fields from a third-surface system metadata record based on ownership.
540
- *
541
- * system_admin and machine principals receive the full record. Human principals
542
- * who pass `canAccessSystemMetadata` receive the full record. All others
543
- * receive only `{ id, created_at, updated_at }`.
544
- *
545
- * @param ctx - Request context
546
- * @param record - The system metadata record to sanitize
547
- * @returns Sanitized record
548
- * @throws never
549
- * @inputSpec record: object — must have id, created_at, updated_at
550
- * @outputSpec object — full record for system_admin/machine/owner, minimal stub for others
551
- * @sideEffects none
552
- * @calledBy sanitizeRecordData (surface='third')
553
- */
411
+ // CIL[END chunk_id="permissions_canaccesssystem_1784066609"]
412
+ // CIL[BEGIN chunk_id="permissions_sanitizesystem_1784066609" type="function" identity="sanitizeSystemMetadata" parent="permissions_classification_1784066609"]
413
+ // CIL[DOC-MACRO purpose="sanitizeSystemMetadata"]
414
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle sanitizeSystemMetadata"]
415
+ // CIL[DOC-CONTEXT module="sanitizeSystemMetadata" layer="unknown" related_files="[]"]
416
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
417
+ // CIL[DOC-MICRO logic_summary="Implements sanitizeSystemMetadata" notable_decisions="[]"]
418
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
419
+ // CIL[DOC-STABILITY stability="stable"]
420
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
421
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
422
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
423
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
424
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
425
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
554
426
  private sanitizeSystemMetadata(ctx: RequestContext, record: any): any {
555
427
  // System admin and system role see everything
556
428
  if (this.isSystemAdmin(ctx) || ctx.principal?.type === 'machine') {
@@ -570,21 +442,21 @@ class _PermissionEngineInternal {
570
442
  }
571
443
  }
572
444
 
573
- /**
574
- * Validates whether the principal may perform `action` on a third-surface record.
575
- * Delegates to `canAccessSystemMetadata` and wraps the result.
576
- *
577
- * @param ctx - Request context
578
- * @param record - The system metadata record
579
- * @param action - CRUD action being validated
580
- * @returns { valid: boolean, error?: string }
581
- * @throws never
582
- * @inputSpec record: object — the record being written/read
583
- * @outputSpec valid: boolean — true if action is permitted
584
- * @outputSpec error: string | undefined — denial reason if !valid
585
- * @sideEffects none
586
- * @calledBy validateUpdatePermissions (surface='third')
587
- */
445
+ // CIL[END chunk_id="permissions_sanitizesystem_1784066609"]
446
+ // CIL[BEGIN chunk_id="permissions_validatesystem_1784066609" type="function" identity="validateSystemMetadataPermissions" parent="permissions_classification_1784066609"]
447
+ // CIL[DOC-MACRO purpose="validateSystemMetadataPermissions"]
448
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle validateSystemMetadataPermissions"]
449
+ // CIL[DOC-CONTEXT module="validateSystemMetadataPermissions" layer="unknown" related_files="[]"]
450
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
451
+ // CIL[DOC-MICRO logic_summary="Implements validateSystemMetadataPermissions" notable_decisions="[]"]
452
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
453
+ // CIL[DOC-STABILITY stability="stable"]
454
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
455
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
456
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
457
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
458
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
459
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
588
460
  private validateSystemMetadataPermissions(ctx: RequestContext, record: any, action: 'create' | 'read' | 'update' | 'delete'): { valid: boolean; error?: string } {
589
461
  if (this.canAccessSystemMetadata(ctx, record, action)) {
590
462
  return { valid: true }
@@ -593,59 +465,40 @@ class _PermissionEngineInternal {
593
465
  return { valid: false, error: 'Insufficient permissions for this operation' }
594
466
  }
595
467
 
596
- // ─── CHUNK_END: PERMISSIONS_THIRD_SURFACE_SYSTEM_METADATA ────────────────────────────────────────────────
597
-
598
- // ─── CHUNK_START: PERMISSIONS_SHARED_HELPERS ──────────────────────────────────────────────
599
-
600
- /**
601
- * Returns true if the principal in `ctx` holds the `system_admin` role.
602
- *
603
- * This is the canonical system_admin check used by all three surfaces and
604
- * the unified principal methods. It is the ONLY mechanism for bypassing
605
- * surface-level permission checks — there is no other bypass in the engine.
606
- *
607
- * @param ctx - Request context with resolved principal
608
- * @returns boolean — true if principal.roles includes 'system_admin'
609
- * @throws never
610
- * @inputSpec ctx.principal: Principal — principal.roles: string[]
611
- * @outputSpec boolean — false if principal is null, anonymous, or has no roles
612
- * @sideEffects none
613
- * @calledBy canAccessRecord, sanitizeRecordData, validateUpdatePermissions,
614
- * canAccessConfigObject, sanitizeConfigObject, sanitizeSystemMetadata,
615
- * canPrincipalAccessRecord
616
- * @testUnit tests/unit/permissions.test.ts — 'isSystemAdmin' describe block
617
- */
468
+ // CIL[END chunk_id="permissions_validatesystem_1784066609"]
469
+ // CIL[BEGIN chunk_id="permissions_issystemadmin_1784066609" type="function" identity="isSystemAdmin" parent="permissions_classification_1784066609"]
470
+ // CIL[DOC-MACRO purpose="isSystemAdmin"]
471
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle isSystemAdmin"]
472
+ // CIL[DOC-CONTEXT module="isSystemAdmin" layer="unknown" related_files="[]"]
473
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
474
+ // CIL[DOC-MICRO logic_summary="Implements isSystemAdmin" notable_decisions="[]"]
475
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
476
+ // CIL[DOC-STABILITY stability="stable"]
477
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
478
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
479
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
480
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
481
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
482
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
618
483
  isSystemAdmin(ctx: RequestContext): boolean {
619
- return ctx.principal?.roles?.includes('system_admin') || false
484
+ return ctx.principal?.roles?.some((r: any) => r?.slug === 'system_admin' || r === 'system_admin') || false
620
485
  }
621
486
 
622
- // ─── CHUNK_END: PERMISSIONS_SHARED_HELPERS ────────────────────────────────────────────────
623
-
624
- // ─── CHUNK_START: PERMISSIONS_CAN_ACCESS_RECORD ──────────────────────────────────────────────
625
-
626
- /**
627
- * Checks whether the principal in `ctx` may perform `action` on `record`.
628
- *
629
- * Routes to the correct surface handler based on `record`'s table name:
630
- * - second surface tables → `canAccessConfigObject`
631
- * - third surface tables → `canAccessSystemMetadata`
632
- * - everything else (first surface) → `canAccessFirstSurfaceRecord`
633
- *
634
- * system_admin always returns true before surface routing.
635
- *
636
- * @param ctx - Request context with resolved principal
637
- * @param record - The record being accessed (used for surface detection only)
638
- * @param action - CRUD action being attempted
639
- * @returns Promise<boolean> — true if access is allowed
640
- * @throws never — all surface handlers catch errors and return false
641
- * @inputSpec ctx.principal: Principal — must be resolved
642
- * @inputSpec record: object — used for table_name/type/item_type extraction
643
- * @inputSpec action: 'create' | 'read' | 'update' | 'delete'
644
- * @outputSpec boolean — false for anonymous principals, missing records, unknown types
645
- * @sideEffects DB read (first surface only): types and people tables
646
- * @calledBy API handlers where explicit access gate is needed (rare — most use sanitize)
647
- * @testUnit tests/unit/permissions.test.ts — 'canAccessRecord' describe block
648
- */
487
+ // CIL[END chunk_id="permissions_issystemadmin_1784066609"]
488
+ // CIL[BEGIN chunk_id="permissions_canaccessrecord_1784066609" type="function" identity="canAccessRecord" parent="permissions_classification_1784066609"]
489
+ // CIL[DOC-MACRO purpose="canAccessRecord"]
490
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle canAccessRecord"]
491
+ // CIL[DOC-CONTEXT module="canAccessRecord" layer="unknown" related_files="[]"]
492
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
493
+ // CIL[DOC-MICRO logic_summary="Implements canAccessRecord" notable_decisions="[]"]
494
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
495
+ // CIL[DOC-STABILITY stability="stable"]
496
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
497
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
498
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
499
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
500
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
501
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
649
502
  async canAccessRecord(
650
503
  ctx: RequestContext,
651
504
  record: any,
@@ -673,27 +526,22 @@ class _PermissionEngineInternal {
673
526
  return this.canAccessFirstSurfaceRecord(ctx, record, action)
674
527
  }
675
528
  }
676
-
677
- /**
678
- * Access check for first-surface (runtime data) records.
679
- *
680
- * Delegates to `resolveFirstSurfacePermissions` to evaluate the design_schema
681
- * permission model. For 'own' access level, additionally checks record ownership
682
- * via `created_by === principal.id`.
683
- *
684
- * Returns false for anonymous principals and any principal without a valid accountId.
685
- *
686
- * @param ctx - Request context
687
- * @param record - The first-surface record being accessed
688
- * @param action - CRUD action
689
- * @returns Promise<boolean>
690
- * @throws never
691
- * @inputSpec ctx.principal: not anonymous, ctx.accountId: non-empty string
692
- * @inputSpec record: must have account_id or item_type/type for schema resolution
693
- * @outputSpec boolean — false for anonymous, missing schema, insufficient permissions
694
- * @sideEffects DB read: types and people tables (via resolveFirstSurfacePermissions)
695
- * @calledBy canAccessRecord (surface='first'), canPrincipalAccessRecord (human branch)
696
- */
529
+ // CIL[END chunk_id="permissions_canaccessrecord_1784066609"]
530
+
531
+ // CIL[BEGIN chunk_id="permissions_canaccessfirst_1784066609" type="function" identity="canAccessFirstSurfaceRecord" parent="permissions_classification_1784066609"]
532
+ // CIL[DOC-MACRO purpose="canAccessFirstSurfaceRecord"]
533
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle canAccessFirstSurfaceRecord"]
534
+ // CIL[DOC-CONTEXT module="canAccessFirstSurfaceRecord" layer="unknown" related_files="[]"]
535
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
536
+ // CIL[DOC-MICRO logic_summary="Implements canAccessFirstSurfaceRecord" notable_decisions="[]"]
537
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
538
+ // CIL[DOC-STABILITY stability="stable"]
539
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
540
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
541
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
542
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
543
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
544
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
697
545
  private async canAccessFirstSurfaceRecord(
698
546
  ctx: RequestContext,
699
547
  record: any,
@@ -746,52 +594,21 @@ class _PermissionEngineInternal {
746
594
  return true
747
595
  }
748
596
 
749
- /**
750
- * Strips and formats a record's fields based on the principal's permissions.
751
- *
752
- * This is the primary output filter called by every API handler before returning
753
- * data to the client. Routes to the correct surface handler, which applies
754
- * field-level filtering from the record's stamped `design_schema`.
755
- *
756
- * system_admin receives the full record unchanged.
757
- *
758
- * For first-surface records with missing `design_schema` or no `record_permissions`,
759
- * returns `{ id }` only — explicit deny, not a pass-through.
760
- *
761
- * @param ctx - Request context with resolved principal
762
- * @param record - The record to sanitize (should be the raw DB row)
763
- * @param typeSlug - Type slug used to classify the surface and look up schema
764
- * if not already stamped on the record. Optional for second/third surfaces.
765
- * @returns Promise<object> — sanitized record safe to return to the client
766
- * @throws never
767
- * @inputSpec ctx.principal: Principal — resolved, may be anonymous
768
- * @inputSpec record: object — raw DB row, must have id at minimum
769
- * @inputSpec typeSlug: string | undefined — slug of the type (e.g. 'item', 'account')
770
- * @outputSpec object — filtered record; field set depends on principal's role permissions
771
- * @outputSpec system_admin: full record unchanged
772
- * @outputSpec unauthenticated: { id, created_at, updated_at } only
773
- * @outputSpec first surface, no schema: { id } only
774
- * @sideEffects DB read (first surface): types and people tables via resolveFirstSurface
775
- * @calledBy All 19 API handlers — this is the most-called method in the engine
776
- * @calls sanitizeFirstSurfaceRecordData | sanitizeConfigObject | sanitizeSystemMetadata
777
- * @testUnit tests/unit/permissions.test.ts — 'sanitizeRecordData' describe block
778
- * @testIntegration tests/integration/admin-data-accounts.test.ts
779
- *
780
- * @example API handler usage
781
- * ```ts
782
- * const sanitized = await sanitizeRecordData(ctx, record, 'item')
783
- * return { data: sanitized }
784
- * ```
785
- *
786
- * @example Import usage (v2-custom/)
787
- * ```ts
788
- * import { sanitizeRecordData } from '../_shared/index'
789
- * const safe = await sanitizeRecordData(ctx, rawRecord, 'ticket')
790
- * ```
791
- */
792
- // ─── CHUNK_END: PERMISSIONS_CAN_ACCESS_RECORD ────────────────────────────────────────────────
793
-
794
- // ─── CHUNK_START: PERMISSIONS_SANITIZE_RECORD_DATA ──────────────────────────────────────────────
597
+ // CIL[END chunk_id="permissions_canaccessfirst_1784066609"]
598
+ // CIL[BEGIN chunk_id="permissions_saniterecorddata_1784066609" type="function" identity="sanitizeRecordData" parent="permissions_classification_1784066609"]
599
+ // CIL[DOC-MACRO purpose="sanitizeRecordData"]
600
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle sanitizeRecordData"]
601
+ // CIL[DOC-CONTEXT module="sanitizeRecordData" layer="unknown" related_files="[]"]
602
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
603
+ // CIL[DOC-MICRO logic_summary="Implements sanitizeRecordData" notable_decisions="[]"]
604
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
605
+ // CIL[DOC-STABILITY stability="stable"]
606
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
607
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
608
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
609
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
610
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
611
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
795
612
  async sanitizeRecordData(
796
613
  ctx: RequestContext,
797
614
  record: any,
@@ -819,33 +636,24 @@ class _PermissionEngineInternal {
819
636
  return this.sanitizeFirstSurfaceRecordData(ctx, record, typeSlug || '')
820
637
  }
821
638
  }
822
- // ─── CHUNK_END: PERMISSIONS_SANITIZE_RECORD_DATA ────────────────────────────────────────────────
823
-
824
- // ─── CHUNK_START: PERMISSIONS_SANITIZE_FIRST_SURFACE ──────────────────────────────────────────────
825
- /**
826
- * Field-level filter and formatter for first-surface (runtime data) records.
827
- *
828
- * Steps:
829
- * 1. Return minimal stub for anonymous principals
830
- * 2. Check `record.design_schema.record_permissions` — deny if missing
831
- * 3. Resolve permissions via `resolveFirstSurfacePermissions`
832
- * 4. Return minimal stub if `!perms.canRead`
833
- * 5. For each field in `record.data`, include only if `fieldPerms.read === true`
834
- * 6. Apply `formatFieldData` from schema-utils if validation_schema specifies a data_type
835
- * 7. Strip `record.metadata` (legacy field, migrated to `data`)
836
- *
837
- * @param ctx - Request context
838
- * @param record - First-surface DB row with design_schema and data fields
839
- * @param typeSlug - Type slug for schema lookup
840
- * @returns Promise<object> — filtered and formatted record
841
- * @throws never
842
- * @inputSpec record.design_schema: object with record_permissions — deny if missing
843
- * @inputSpec record.data: object — JSONB data fields; only permitted fields returned
844
- * @outputSpec object — sanitized record matching the principal's field permissions
845
- * @sideEffects DB read: types and people via resolveFirstSurfacePermissions
846
- * @calledBy sanitizeRecordData (surface='first')
847
- * @calls resolveFirstSurfacePermissions, schema-utils.formatFieldData
848
- */
639
+ // CIL[END chunk_id="permissions_saniterecorddata_1784066609"]
640
+
641
+ // CIL[BEGIN chunk_id="permissions_sanitizerecord_1784066609" type="function" identity="sanitizeFirstSurfaceRecordData" parent="permissions_classification_1784066609"]
642
+ // CIL[DOC-LEARNING topic="runtime-type:generic/permissions" content="Record visibility is controlled by record.design_schema.record_permissions and each field's fields[name].permissions. The function checks the current principal role against allowed actions create/read/update/delete. If record_permissions is missing, the record is reduced to {id}."]
643
+ // CIL[DOC-NOTE type="design_schema_caveat" content="Every stamped record must carry its type's record_permissions and per-field permissions or users will see empty records. Permissions are inherited from the type at creation time; updating the type does not retroactively update existing records unless migrations run."]
644
+ // CIL[DOC-MACRO purpose="sanitizeFirstSurfaceRecordData"]
645
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle sanitizeFirstSurfaceRecordData"]
646
+ // CIL[DOC-CONTEXT module="sanitizeFirstSurfaceRecordData" layer="unknown" related_files="[]"]
647
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
648
+ // CIL[DOC-MICRO logic_summary="Implements sanitizeFirstSurfaceRecordData" notable_decisions="[]"]
649
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
650
+ // CIL[DOC-STABILITY stability="stable"]
651
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
652
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
653
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
654
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
655
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
656
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
849
657
  private async sanitizeFirstSurfaceRecordData(
850
658
  ctx: RequestContext,
851
659
  record: any,
@@ -886,6 +694,9 @@ class _PermissionEngineInternal {
886
694
  // design_schema controls what the principal can do. No permissions granted = none given.
887
695
  const designSchema = record.design_schema
888
696
  if (!designSchema || !designSchema.record_permissions) {
697
+ if (['logs', 'test_runs'].includes(typeSlug) && record.account_id === ctx.accountId) {
698
+ return record
699
+ }
889
700
  return { id: record.id }
890
701
  }
891
702
 
@@ -906,6 +717,14 @@ class _PermissionEngineInternal {
906
717
  }
907
718
  }
908
719
 
720
+ // When no field definitions are provided, allow the full data object at the record read level.
721
+ // Field-level permissions can still be added later by populating design_schema.fields.
722
+ if (Object.keys(designSchema.fields || {}).length === 0) {
723
+ const sanitized = { ...record }
724
+ if (sanitized.metadata) delete sanitized.metadata
725
+ return sanitized
726
+ }
727
+
909
728
  // Clone record to avoid mutation
910
729
  const sanitized = { ...record }
911
730
 
@@ -942,48 +761,22 @@ class _PermissionEngineInternal {
942
761
 
943
762
  return sanitized
944
763
  }
945
- // ─── CHUNK_END: PERMISSIONS_SANITIZE_FIRST_SURFACE ────────────────────────────────────────────────
946
-
947
- // ─── CHUNK_START: PERMISSIONS_VALIDATE_UPDATE ──────────────────────────────────────────────
948
- /**
949
- * Validates that the principal has write permission for every field in `updateData`,
950
- * and sanitizes the data using the validation schema before returning it.
951
- *
952
- * Routes to the correct surface handler. system_admin bypasses all checks and
953
- * receives `updateData` unchanged (with `sanitizedData` set to `updateData`).
954
- *
955
- * For first-surface records:
956
- * - Each field in `updateData.data` must have `fieldPerms.write === true`
957
- * - Fields are sanitized via `sanitizeFieldData` from schema-utils
958
- * - Returns `{ valid: false, error }` on the first denied or invalid field
959
- *
960
- * @param ctx - Request context with resolved principal
961
- * @param updateData - The payload being written (may contain `data` and/or `metadata`)
962
- * @param existingRecord - The current DB row (used for schema + account_id resolution)
963
- * @param typeSlug - Type slug for surface classification and schema lookup
964
- * @returns Promise<{ valid: boolean, error?: string, sanitizedData?: any }>
965
- * @throws never
966
- * @inputSpec ctx.principal: Principal — resolved, non-anonymous required for first surface
967
- * @inputSpec updateData: object — payload with data: {} and/or metadata: {}
968
- * @inputSpec existingRecord: object — must have design_schema, account_id
969
- * @outputSpec valid: boolean — false on first permission or validation failure
970
- * @outputSpec error: string | undefined — field name + reason when !valid
971
- * @outputSpec sanitizedData: object | undefined — cleaned payload when valid
972
- * @sideEffects DB read (first surface): types and people via resolveFirstSurfacePermissions
973
- * @calledBy admin-data.ts (update handler), and any handler that accepts user writes
974
- * @calls validateFirstSurfaceUpdatePermissions | validateConfigObjectPermissions |
975
- * validateSystemMetadataPermissions
976
- * @testUnit tests/unit/permissions.test.ts — 'validateUpdatePermissions' describe block
977
- *
978
- * @example API handler usage
979
- * ```ts
980
- * const { valid, error, sanitizedData } = await validateUpdatePermissions(
981
- * ctx, body, existingRecord, 'item'
982
- * )
983
- * if (!valid) return { error }
984
- * await ctx.db.from('items').update(sanitizedData).eq('id', id)
985
- * ```
986
- */
764
+
765
+ // CIL[END chunk_id="permissions_sanitizerecord_1784066609"]
766
+ // CIL[BEGIN chunk_id="permissions_validateupdatepermissions_1784066609" type="function" identity="validateUpdatePermissions" parent="permissions_classification_1784066609"]
767
+ // CIL[DOC-MACRO purpose="validateUpdatePermissions"]
768
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle validateUpdatePermissions"]
769
+ // CIL[DOC-CONTEXT module="validateUpdatePermissions" layer="unknown" related_files="[]"]
770
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
771
+ // CIL[DOC-MICRO logic_summary="Implements validateUpdatePermissions" notable_decisions="[]"]
772
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
773
+ // CIL[DOC-STABILITY stability="stable"]
774
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
775
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
776
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
777
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
778
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
779
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
987
780
  async validateUpdatePermissions(
988
781
  ctx: RequestContext,
989
782
  updateData: any,
@@ -1012,30 +805,22 @@ class _PermissionEngineInternal {
1012
805
  return this.validateFirstSurfaceUpdatePermissions(ctx, updateData, existingRecord, typeSlug || '')
1013
806
  }
1014
807
  }
1015
- // ─── CHUNK_END: PERMISSIONS_VALIDATE_UPDATE ────────────────────────────────────────────────
1016
-
1017
- // ─── CHUNK_START: PERMISSIONS_VALIDATE_FIRST_SURFACE ──────────────────────────────────────────────
1018
- /**
1019
- * Field-level write validation and sanitization for first-surface update payloads.
1020
- *
1021
- * Checks every field in `updateData.data` (and legacy `updateData.metadata`) against
1022
- * the principal's write permissions. Sanitizes each permitted field through
1023
- * `sanitizeFieldData` for type coercion and constraint validation. Returns on
1024
- * the first denied or invalid field — does not accumulate errors.
1025
- *
1026
- * @param ctx - Request context
1027
- * @param updateData - Incoming update payload
1028
- * @param existingRecord - Existing DB row with design_schema stamped at creation
1029
- * @param typeSlug - Type slug for schema lookup
1030
- * @returns Promise<{ valid: boolean, error?: string, sanitizedData?: any }>
1031
- * @throws never
1032
- * @inputSpec existingRecord.design_schema.record_permissions — deny if missing
1033
- * @inputSpec updateData.data: object — all fields must have fieldPerms.write=true
1034
- * @outputSpec sanitizedData: object — only present when valid=true
1035
- * @sideEffects DB read: types and people via resolveFirstSurfacePermissions
1036
- * @calledBy validateUpdatePermissions (surface='first')
1037
- * @calls resolveFirstSurfacePermissions, schema-utils.sanitizeFieldData
1038
- */
808
+ // CIL[END chunk_id="permissions_validateupdatepermissions_1784066609"]
809
+
810
+ // CIL[BEGIN chunk_id="permissions_validatefirstupdate_1784066609" type="function" identity="validateFirstSurfaceUpdatePermissions" parent="permissions_classification_1784066609"]
811
+ // CIL[DOC-MACRO purpose="validateFirstSurfaceUpdatePermissions"]
812
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle validateFirstSurfaceUpdatePermissions"]
813
+ // CIL[DOC-CONTEXT module="validateFirstSurfaceUpdatePermissions" layer="unknown" related_files="[]"]
814
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
815
+ // CIL[DOC-MICRO logic_summary="Implements validateFirstSurfaceUpdatePermissions" notable_decisions="[]"]
816
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
817
+ // CIL[DOC-STABILITY stability="stable"]
818
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
819
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
820
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
821
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
822
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
823
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1039
824
  private async validateFirstSurfaceUpdatePermissions(
1040
825
  ctx: RequestContext,
1041
826
  updateData: any,
@@ -1066,6 +851,11 @@ class _PermissionEngineInternal {
1066
851
  return { valid: false, error: 'Insufficient permissions to update this record' }
1067
852
  }
1068
853
 
854
+ // When no field definitions are provided, allow the whole update through at the record update level.
855
+ if (Object.keys(designSchema.fields || {}).length === 0) {
856
+ return { valid: true, sanitizedData: updateData }
857
+ }
858
+
1069
859
  // Check field-level permissions and sanitize data
1070
860
  const sanitizedData: any = {}
1071
861
  const validationSchema = existingRecord.validation_schema || {}
@@ -1125,51 +915,22 @@ class _PermissionEngineInternal {
1125
915
 
1126
916
  return { valid: true, sanitizedData }
1127
917
  }
1128
- // ─── CHUNK_END: PERMISSIONS_VALIDATE_FIRST_SURFACE ────────────────────────────────────────────────
1129
-
1130
- // ─── CHUNK_START: PERMISSIONS_CAN_PRINCIPAL_ACCESS_RECORD ──────────────────────────────────────────────
1131
-
1132
- /**
1133
- * Unified permission check for all principal types (human, machine, cron, trigger).
1134
- *
1135
- * This is the preferred method when you have a `Principal` directly rather than
1136
- * a full `RequestContext`. Used by the Unified Principal Architecture to check
1137
- * access without constructing a fake context.
1138
- *
1139
- * Resolution:
1140
- * 1. system_admin (human with 'system_admin' role) → always true
1141
- * 2. machine principal → scope check via `checkMachineScope`
1142
- * 3. human principal with accountId → `canAccessFirstSurfaceRecord` (constructs minimal ctx)
1143
- * 4. all others → false
1144
- *
1145
- * @param principal - The fully resolved Principal from `resolvePrincipal()`
1146
- * @param record - The record being accessed; must include account_id and type for scope matching
1147
- * @param action - CRUD action being attempted
1148
- * @returns Promise<boolean> — true if the principal may perform the action
1149
- * @throws never
1150
- * @inputSpec principal: Principal — must be resolved (not ANONYMOUS_PRINCIPAL for useful results)
1151
- * @inputSpec record.account_id: string — required for human principals
1152
- * @inputSpec record.type: string | undefined — used for machine scope matching
1153
- * @outputSpec boolean
1154
- * @sideEffects DB read (human principal): types and people tables
1155
- * @calledBy Handlers that receive a Principal directly (e.g. CLI, import callers)
1156
- * @calls checkMachineScope, canAccessFirstSurfaceRecord
1157
- * @testUnit tests/unit/permissions.test.ts — 'canPrincipalAccessRecord' describe block
1158
- *
1159
- * @example Import usage (v2-custom/)
1160
- * ```ts
1161
- * import { PermissionEngine } from '../_shared/index'
1162
- * const allowed = await PermissionEngine.canPrincipalAccessRecord(
1163
- * principal, { account_id: accountId, type: 'item' }, 'create'
1164
- * )
1165
- * ```
1166
- *
1167
- * @example CLI usage
1168
- * ```bash
1169
- * # Access checks happen automatically when CLI constructs CoreContext
1170
- * spine items create --data '{"title":"Test"}'
1171
- * ```
1172
- */
918
+
919
+ // CIL[END chunk_id="permissions_validatefirstupdate_1784066609"]
920
+ // CIL[BEGIN chunk_id="permissions_canprincipalaccessrecord_1784066609" type="function" identity="canPrincipalAccessRecord" parent="permissions_classification_1784066609"]
921
+ // CIL[DOC-MACRO purpose="canPrincipalAccessRecord"]
922
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle canPrincipalAccessRecord"]
923
+ // CIL[DOC-CONTEXT module="canPrincipalAccessRecord" layer="unknown" related_files="[]"]
924
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
925
+ // CIL[DOC-MICRO logic_summary="Implements canPrincipalAccessRecord" notable_decisions="[]"]
926
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
927
+ // CIL[DOC-STABILITY stability="stable"]
928
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
929
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
930
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
931
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
932
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
933
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1173
934
  async canPrincipalAccessRecord(
1174
935
  principal: Principal,
1175
936
  record: { account_id: string; type?: string; [key: string]: any },
@@ -1203,33 +964,22 @@ class _PermissionEngineInternal {
1203
964
 
1204
965
  return false
1205
966
  }
1206
- // ─── CHUNK_END: PERMISSIONS_CAN_PRINCIPAL_ACCESS_RECORD ────────────────────────────────────────────────
1207
-
1208
- // ─── CHUNK_START: PERMISSIONS_MACHINE_SCOPE ──────────────────────────────────────────────
1209
- /**
1210
- * Evaluates whether a machine principal's scopes permit the requested action.
1211
- *
1212
- * Scope matching supports three patterns (evaluated in order):
1213
- * 1. Exact match: `'items:read'` matches `'items:read'`
1214
- * 2. Wildcard action: `'items:*'` matches any action on `items`
1215
- * 3. Global wildcard: `'*:*'` matches any resource and any action
1216
- *
1217
- * The required scope is constructed as `<record.type>:<action>`. If `record.type`
1218
- * is absent, `'resource'` is used as the resource name.
1219
- *
1220
- * @param principal - Machine principal (principal.type must be 'machine')
1221
- * @param record - The record being accessed (record.type used as resource name)
1222
- * @param action - The CRUD action string
1223
- * @returns boolean — true if any scope in principal.scopes grants the action
1224
- * @throws never
1225
- * @inputSpec principal.type: 'machine' — returns false for non-machine principals
1226
- * @inputSpec principal.scopes: string[] — list of granted scope strings
1227
- * @inputSpec record.type: string | undefined — resource name portion of scope check
1228
- * @outputSpec boolean
1229
- * @sideEffects none
1230
- * @calledBy canPrincipalAccessRecord (machine branch)
1231
- * @testUnit tests/unit/permissions.test.ts — 'checkMachineScope' describe block
1232
- */
967
+ // CIL[END chunk_id="permissions_canprincipalaccessrecord_1784066609"]
968
+
969
+ // CIL[BEGIN chunk_id="permissions_checkmachinescope_1784066609" type="function" identity="checkMachineScope" parent="permissions_classification_1784066609"]
970
+ // CIL[DOC-MACRO purpose="checkMachineScope"]
971
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle checkMachineScope"]
972
+ // CIL[DOC-CONTEXT module="checkMachineScope" layer="unknown" related_files="[]"]
973
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
974
+ // CIL[DOC-MICRO logic_summary="Implements checkMachineScope" notable_decisions="[]"]
975
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
976
+ // CIL[DOC-STABILITY stability="stable"]
977
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
978
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
979
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
980
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
981
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
982
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1233
983
  private checkMachineScope(
1234
984
  principal: Principal,
1235
985
  record: any,
@@ -1252,34 +1002,22 @@ class _PermissionEngineInternal {
1252
1002
 
1253
1003
  return false
1254
1004
  }
1255
- // ─── CHUNK_END: PERMISSIONS_MACHINE_SCOPE ────────────────────────────────────────────────
1256
-
1257
- // ─── CHUNK_START: PERMISSIONS_PERMISSION_SUMMARY ──────────────────────────────────────────────
1258
- /**
1259
- * Returns a structured summary of a principal's permission posture for use
1260
- * in audit log entries.
1261
- *
1262
- * Does not perform any access check — purely descriptive. The returned object
1263
- * is safe to serialize into the `metadata` column of the `logs` table.
1264
- *
1265
- * @param principal - The resolved principal to summarize
1266
- * @returns object — summary safe for audit log serialization
1267
- * @throws never
1268
- * @inputSpec principal: Principal — any resolved principal including ANONYMOUS
1269
- * @outputSpec { type, roles, is_system_admin } for human principals
1270
- * @outputSpec { type, machine_type, scopes, is_internal } for machine principals
1271
- * @outputSpec { type: 'unknown' } for all other types
1272
- * @sideEffects none
1273
- * @calledBy audit.ts (emitAudit), any handler that logs permission context
1274
- * @testUnit tests/unit/permissions.test.ts — 'getPrincipalPermissionSummary' describe block
1275
- *
1276
- * @example
1277
- * ```ts
1278
- * await emitAudit(ctx, 'record.read', record.id, {
1279
- * permissions: PermissionEngine.getPrincipalPermissionSummary(ctx.principal)
1280
- * })
1281
- * ```
1282
- */
1005
+
1006
+ // CIL[END chunk_id="permissions_checkmachinescope_1784066609"]
1007
+ // CIL[BEGIN chunk_id="permissions_getprincipalsummary_1784066609" type="function" identity="getPrincipalPermissionSummary" parent="permissions_classification_1784066609"]
1008
+ // CIL[DOC-MACRO purpose="getPrincipalPermissionSummary"]
1009
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle getPrincipalPermissionSummary"]
1010
+ // CIL[DOC-CONTEXT module="getPrincipalPermissionSummary" layer="unknown" related_files="[]"]
1011
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
1012
+ // CIL[DOC-MICRO logic_summary="Implements getPrincipalPermissionSummary" notable_decisions="[]"]
1013
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1014
+ // CIL[DOC-STABILITY stability="stable"]
1015
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
1016
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1017
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1018
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1019
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1020
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1283
1021
  getPrincipalPermissionSummary(principal: Principal): object {
1284
1022
  if (principal.type === 'human') {
1285
1023
  return {
@@ -1301,49 +1039,27 @@ class _PermissionEngineInternal {
1301
1039
  return { type: 'unknown' }
1302
1040
  }
1303
1041
  }
1304
- // ─── CHUNK_END: PERMISSIONS_PERMISSION_SUMMARY ────────────────────────────────────────────────
1305
-
1306
- // ─── CHUNK_START: PERMISSIONS_SINGLETON_AND_EXPORTS ──────────────────────────────────────────────
1307
- // ─── SINGLETON EXPORT ────────────────────────────────────────────────────────
1308
-
1309
- /**
1310
- * The single shared PermissionEngine instance.
1311
- *
1312
- * This is the ONLY export that should be used for permission checks. Import this
1313
- * directly or use the named legacy aliases below. Do not instantiate
1314
- * `_PermissionEngineInternal` yourself.
1315
- *
1316
- * @stability stable
1317
- * @audience both
1318
- * @calledBy All 19 API handlers, tests, and custom code in v2-custom/
1319
- *
1320
- * @example API handler
1321
- * ```ts
1322
- * import { PermissionEngine } from './_shared/permissions'
1323
- * const sanitized = await PermissionEngine.sanitizeRecordData(ctx, record, 'item')
1324
- * ```
1325
- *
1326
- * @example Import usage (v2-custom/)
1327
- * ```ts
1328
- * import { PermissionEngine } from '../_shared/index'
1329
- * const allowed = await PermissionEngine.canPrincipalAccessRecord(principal, record, 'read')
1330
- * ```
1331
- */
1332
- export const PermissionEngine: _PermissionEngineInternal = _PermissionEngineInternal.getInstance()
1333
1042
 
1334
- // ─── LEGACY EXPORTS ───────────────────────────────────────────────────────────
1043
+ // CIL[END chunk_id="permissions_getprincipalsummary_1784066609"]
1044
+ // CIL[BEGIN chunk_id="permissions_exports_1784066609" type="exports" identity="exports" parent="none"]
1045
+ // CIL[DOC-MACRO purpose="exports"]
1046
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle exports"]
1047
+ // CIL[DOC-CONTEXT module="exports" layer="unknown" related_files="[]"]
1048
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
1049
+ // CIL[DOC-MICRO logic_summary="Implements exports" notable_decisions="[]"]
1050
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1051
+ // CIL[DOC-STABILITY stability="stable"]
1052
+ // CIL[DOC-VECTOR type="context" content="Layer: unknown. Direct callers: 0. Direct calls: 0. Related files: none."]
1053
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1054
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1055
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1056
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1057
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1058
+ export const PermissionEngine: _PermissionEngineInternal = _PermissionEngineInternal.getInstance()
1335
1059
 
1336
- /**
1337
- * Legacy named exports — bound methods on the singleton for backward compatibility.
1338
- * Prefer importing `PermissionEngine` and calling methods on it directly.
1339
- * These will be removed in a future version.
1340
- *
1341
- * @deprecated Use `PermissionEngine.<methodName>()` instead.
1342
- * @stability internal
1343
- */
1344
1060
  export const resolveFirstSurfacePermissions = PermissionEngine.resolveFirstSurfacePermissions.bind(PermissionEngine)
1345
1061
  export const isSystemAdmin = PermissionEngine.isSystemAdmin.bind(PermissionEngine)
1346
1062
  export const canAccessRecord = PermissionEngine.canAccessRecord.bind(PermissionEngine)
1347
1063
  export const sanitizeRecordData = PermissionEngine.sanitizeRecordData.bind(PermissionEngine)
1348
1064
  export const validateUpdatePermissions = PermissionEngine.validateUpdatePermissions.bind(PermissionEngine)
1349
- // ─── CHUNK_END: PERMISSIONS_SINGLETON_AND_EXPORTS ────────────────────────────────────────────────
1065
+ // CIL[END chunk_id="permissions_exports_1784066609"]