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,90 +1,95 @@
1
- /**
2
- * @module schema-utils
3
- * @audience both
4
- * @layer shared-core
5
- * @stability stable
6
- *
7
- * Schema generation and field-level data transformation for all Spine types.
8
- *
9
- * Three public functions form the core contract:
10
- * - `generateValidationSchema` — derives a runtime validation schema from a
11
- * `design_schema`, stripping display/permission info and keeping only
12
- * structural constraints.
13
- * - `sanitizeFieldData` — coerces and validates a single field value for
14
- * write (create/update) operations. Throws on invalid data.
15
- * - `formatFieldData` — converts a stored field value to a human-readable
16
- * display string for read operations. Never throws.
17
- * - `transformRecordData` — applies sanitize or format to all fields in a
18
- * record using a pre-generated ValidationSchema.
19
- *
20
- * These are called by `permissions.ts` (`validateFirstSurfaceUpdatePermissions`
21
- * and `sanitizeFirstSurfaceRecordData`) — do not call them directly from API
22
- * handlers. Use `PermissionEngine.sanitizeRecordData` / `validateUpdatePermissions`.
23
- *
24
- * INVARIANT: all sanitize functions throw on invalid data. Callers must catch
25
- * errors and convert them to field-level validation error messages.
26
- * INVARIANT: all format functions return the raw data unchanged if formatting
27
- * is not applicable (never throw, never return null for non-null input).
28
- *
29
- * @seeAlso permissions.ts (primary caller of sanitizeFieldData, formatFieldData)
30
- * @seeAlso src/types/types.ts (FieldDefinition interface)
31
- * @seeAlso index.ts (not re-exported — internal to core; use PermissionEngine)
32
- */
33
1
 
34
- import { FieldDefinition } from '../../src/types/types'
35
2
 
36
- // ─── TYPES ───────────────────────────────────────────────────────────────
37
-
38
- /**
39
- * Structural-only validation schema derived from a `design_schema`.
40
- *
41
- * Contains one entry per field with the field's `data_type` and any explicit
42
- * `validation` constraints. Display properties (`display_type`, views, sections)
43
- * and permission properties are stripped. Used as input to `sanitizeFieldData`
44
- * and `formatFieldData`.
45
- *
46
- * @inputSpec none — output type of generateValidationSchema
47
- * @outputSpec fields: Record<fieldName, { data_type, required?, ...constraints }>
48
- * @calledBy generateValidationSchema (producer), transformRecordData,
49
- * permissions.ts validateFirstSurfaceUpdatePermissions (consumer)
50
- */
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+ // CIL[BEGIN chunk_id="schemautils_imports_1784020784" type="module_header" identity="imports" parent="none"]
39
+ // CIL[DOC-MACRO purpose="Import field definition type for schema utilities"]
40
+ // CIL[DOC-RESPONSIBILITY responsibility="Import FieldDefinition interface from types"]
41
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/migrations/025_retype_threads_messages.sql\",\"\\\"]"]
42
+ // CIL[DOC-CONTRACT inputs="none" outputs="FieldDefinition type" errors="none" side_effects="none"]
43
+ // CIL[DOC-MICRO logic_summary="Import type definition for field structure" notable_decisions="[]"]
44
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
45
+ // CIL[DOC-STABILITY stability="stable"]
46
+ // CIL[DOC-AUDIENCE audience="developer"]
47
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/migrations/025_retype_threads_messages.sql, \."]
48
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
49
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
50
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
51
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
52
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
53
+ import { FieldDefinition } from '../../src/types/types'
54
+ // CIL[END chunk_id="schemautils_imports_1784020784"]
55
+ // CIL[BEGIN chunk_id="schemautils_validationinterface_1784020784" type="interface" identity="ValidationSchema" parent="none"]
56
+ // CIL[DOC-MACRO purpose="Define validation schema interface for field validation"]
57
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide structure for validation rules per field"]
58
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[\".framework/src/components/layout/Sidebar.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
59
+ // CIL[DOC-CONTRACT inputs="none" outputs="ValidationSchema interface" errors="none" side_effects="none"]
60
+ // CIL[DOC-MICRO logic_summary="Interface with fields containing data_type and validation rules" notable_decisions="[]"]
61
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
62
+ // CIL[DOC-STABILITY stability="stable"]
63
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/src/components/layout/Sidebar.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, \."]
64
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
65
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
66
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
67
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
68
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
51
69
  export interface ValidationSchema {
52
70
  fields: Record<string, {
53
71
  data_type: string
54
72
  required?: boolean
55
- [key: string]: any // Type-specific validation properties
73
+ [key: string]: any
56
74
  }>
57
75
  }
58
-
59
- // ─── PUBLIC API ──────────────────────────────────────────────────────────────
60
-
61
- /**
62
- * Derives a `ValidationSchema` from a `design_schema` by extracting only
63
- * structural constraints (data_type, required, validation.*) and discarding
64
- * display, permission, and view configuration.
65
- *
66
- * The resulting schema is used to drive `sanitizeFieldData` and
67
- * `formatFieldData` for every field in a record. It is generated once per
68
- * type and passed to `transformRecordData` for bulk field processing.
69
- *
70
- * @param designSchema - The full design_schema object from a type record
71
- * @returns ValidationSchema with one entry per field
72
- * @throws never — returns empty schema if designSchema.fields is missing
73
- * @inputSpec designSchema.fields: Record<fieldName, FieldDefinition> — must match
74
- * the FieldDefinition interface from src/types/types.ts
75
- * @inputSpec designSchema.fields[x].data_type: string — required in every field
76
- * @outputSpec ValidationSchema.fields: Record<string, { data_type, required, ...constraints }>
77
- * @sideEffects none
78
- * @calledBy permissions.ts (validateFirstSurfaceUpdatePermissions), any caller
79
- * needing a validation schema without the full design_schema overhead
80
- * @testUnit tests/unit/schema-utils.test.ts — 'generateValidationSchema' describe block
81
- *
82
- * @example
83
- * ```ts
84
- * const schema = generateValidationSchema(record.design_schema)
85
- * const cleaned = transformRecordData(record.data, schema, 'sanitize')
86
- * ```
87
- */
76
+ // CIL[END chunk_id="schemautils_validationinterface_1784020784"]
77
+ // CIL[BEGIN chunk_id="schemautils_generatevalidationschema_1784020784" type="function" identity="generateValidationSchema" parent="none"]
78
+ // CIL[DOC-LEARNING topic="runtime-type:generic/validation_generation" content="generateValidationSchema builds a Zod-compatible validation schema from design_schema.fields. It uses data_type, required, validation, options, and reference fields to produce row-level and data-level validators."]
79
+ // CIL[DOC-NOTE type="design_schema_caveat" content="Unsupported data_types or validation keys are ignored. Reference fields need reference_kind and reference_type or validation will not enforce foreign keys."]
80
+ // CIL[DOC-MACRO purpose="Generate validation schema from design schema"]
81
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert design schema to validation rules"]
82
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\"\\\"]"]
83
+ // CIL[DOC-CONTRACT inputs="none" outputs="ValidationSchema" errors="none" side_effects="none"]
84
+ // CIL[DOC-MICRO logic_summary="Extracts field definitions and builds validation schema" notable_decisions="[]"]
85
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
86
+ // CIL[DOC-STABILITY stability="stable"]
87
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/TypeDetailPage.tsx, .framework/src/pages/admin/RolesPage.tsx, \."]
88
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
89
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
90
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
91
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
92
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
88
93
  export function generateValidationSchema(designSchema: any): ValidationSchema {
89
94
  const validationSchema: ValidationSchema = {
90
95
  fields: {}
@@ -97,23 +102,19 @@ export function generateValidationSchema(designSchema: any): ValidationSchema {
97
102
  for (const [fieldName, fieldDef] of Object.entries(designSchema.fields)) {
98
103
  const field = fieldDef as FieldDefinition
99
104
 
100
- // Extract only structural validation properties exactly as declared
101
105
  const validationField: any = {
102
106
  data_type: field.data_type,
103
107
  required: field.required
104
108
  }
105
109
 
106
- // Add explicit validation constraints exactly as declared
107
110
  if (field.validation) {
108
111
  Object.assign(validationField, field.validation)
109
112
  }
110
113
 
111
- // Add type-specific constraint properties (moved out of validation for clarity)
112
114
  if (field.options) {
113
115
  validationField.options = field.options
114
116
  }
115
117
 
116
- // Add reference properties if they exist
117
118
  if (field.data_type === 'reference' && field.validation) {
118
119
  if (field.validation.reference_kind) validationField.reference_kind = field.validation.reference_kind
119
120
  if (field.validation.reference_type) validationField.reference_type = field.validation.reference_type
@@ -124,41 +125,21 @@ export function generateValidationSchema(designSchema: any): ValidationSchema {
124
125
 
125
126
  return validationSchema
126
127
  }
127
-
128
- /**
129
- * Coerces and validates a single field value for a write (create/update) operation.
130
- *
131
- * Dispatches to a type-specific sanitizer based on `data_type`. Every sanitizer:
132
- * - Coerces the input to the correct type
133
- * - Applies constraint validation (min/max/length/pattern/options)
134
- * - Throws a descriptive `Error` on the first validation failure
135
- * - Returns the cleaned value on success
136
- *
137
- * Unknown `data_type` values pass through unchanged (no throw).
138
- *
139
- * @param data - Raw field value from the request body
140
- * @param data_type - The field's declared data_type from design_schema.fields[x]
141
- * @param validation - Optional validation constraints from the field definition
142
- * @returns Sanitized value in the correct type for storage
143
- * @throws Error — descriptive message naming the field constraint violated
144
- * @inputSpec data: any — null and undefined are returned as-is without sanitization
145
- * @inputSpec data_type: string — one of the 21 supported type keys (see switch below)
146
- * @inputSpec validation: object | undefined — type-specific constraints
147
- * @outputSpec any — coerced value matching the data_type storage format
148
- * @sideEffects none
149
- * @calledBy permissions.ts (validateFirstSurfaceUpdatePermissions, per-field loop)
150
- * @calls sanitizeText | sanitizeTextarea | sanitizeEmail | sanitizeNumber | etc.
151
- * @testUnit tests/unit/schema-utils.test.ts — 'sanitizeFieldData' describe block
152
- *
153
- * @example
154
- * ```ts
155
- * const clean = sanitizeFieldData('hello@EXAMPLE.COM', 'email')
156
- * // → 'hello@example.com'
157
- *
158
- * sanitizeFieldData('not-a-url', 'url')
159
- * // throws Error('Invalid URL format')
160
- * ```
161
- */
128
+ // CIL[END chunk_id="schemautils_generatevalidationschema_1784020784"]
129
+ // CIL[BEGIN chunk_id="schemautils_sanitizefielddata_1784020784" type="function" identity="sanitizeFieldData" parent="none"]
130
+ // CIL[DOC-MACRO purpose="Dispatch sanitization based on field data type"]
131
+ // CIL[DOC-RESPONSIBILITY responsibility="Route data to appropriate sanitizer function"]
132
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[\".framework/migrations/025_retype_threads_messages.sql\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
133
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized data" errors="validation errors" side_effects="none"]
134
+ // CIL[DOC-MICRO logic_summary="Switch statement routes to type-specific sanitizers" notable_decisions="[]"]
135
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
136
+ // CIL[DOC-STABILITY stability="stable"]
137
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/025_retype_threads_messages.sql, .framework/src/pages/admin/IntegrationsPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
138
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
139
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
140
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
141
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
142
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
162
143
  export function sanitizeFieldData(
163
144
  data: any,
164
145
  data_type: string,
@@ -217,40 +198,21 @@ export function sanitizeFieldData(
217
198
  return data
218
199
  }
219
200
  }
220
-
221
- /**
222
- * Converts a stored field value to a human-readable display string.
223
- *
224
- * Dispatches to a type-specific formatter based on `data_type`. Formatters
225
- * never throw — if the data cannot be formatted, the raw value is returned.
226
- * Only types with meaningful display transformations have a case; all others
227
- * fall through to the default (return data unchanged).
228
- *
229
- * @param data - Stored field value (from DB, post-sanitization)
230
- * @param data_type - The field's declared data_type
231
- * @param context - Optional context for type-specific formatting (e.g.
232
- * `context.currency_code` for currency fields, `context.field` for boolean
233
- * contextual labels like 'Active'/'Inactive')
234
- * @returns Formatted display value
235
- * @throws never
236
- * @inputSpec data: any — null and undefined are returned as-is
237
- * @inputSpec data_type: string — one of 21 supported keys; unknown → pass-through
238
- * @inputSpec context: object | undefined — type-specific display hints
239
- * @outputSpec any — display-ready value; string for most types, raw data for pass-through
240
- * @sideEffects none
241
- * @calledBy permissions.ts (sanitizeFirstSurfaceRecordData, per-field loop)
242
- * @calls formatJson | formatDate | formatDatetime | formatCurrency | etc.
243
- * @testUnit tests/unit/schema-utils.test.ts — 'formatFieldData' describe block
244
- *
245
- * @example
246
- * ```ts
247
- * formatFieldData('2024-01-15', 'date')
248
- * // → 'January 15, 2024'
249
- *
250
- * formatFieldData(1234.5, 'currency', { currency_code: 'USD' })
251
- * // → '$1,234.50'
252
- * ```
253
- */
201
+ // CIL[END chunk_id="schemautils_sanitizefielddata_1784020784"]
202
+ // CIL[BEGIN chunk_id="schemautils_formatfielddata_1784020784" type="function" identity="formatFieldData" parent="none"]
203
+ // CIL[DOC-MACRO purpose="Dispatch formatting based on field data type"]
204
+ // CIL[DOC-RESPONSIBILITY responsibility="Route data to appropriate formatter function"]
205
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[\".framework/migrations/025_retype_threads_messages.sql\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
206
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted data" errors="none" side_effects="none"]
207
+ // CIL[DOC-MICRO logic_summary="Switch statement routes to type-specific formatters" notable_decisions="[]"]
208
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
209
+ // CIL[DOC-STABILITY stability="stable"]
210
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/025_retype_threads_messages.sql, .framework/src/pages/admin/IntegrationsPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
211
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
212
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
213
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
214
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
215
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
254
216
  export function formatFieldData(
255
217
  data: any,
256
218
  data_type: string,
@@ -285,37 +247,21 @@ export function formatFieldData(
285
247
  return data
286
248
  }
287
249
  }
288
-
289
- /**
290
- * Applies `sanitizeFieldData` or `formatFieldData` to all fields in a record,
291
- * using the ValidationSchema for per-field type and constraint information.
292
- *
293
- * Fields not present in the schema are passed through unchanged. This is
294
- * intentional — unknown fields are not rejected here; the PermissionEngine
295
- * handles field-level access control separately.
296
- *
297
- * @param data - Key/value record of field names to raw or stored values
298
- * @param validationSchema - Schema from `generateValidationSchema`
299
- * @param operation - 'sanitize' for write path; 'format' for display path
300
- * @param context - Optional context passed through to formatFieldData
301
- * @returns Transformed record with the same keys
302
- * @throws Error (sanitize mode only) — if any field fails validation
303
- * @inputSpec data: Record<string, any> — flat field map
304
- * @inputSpec validationSchema: ValidationSchema — from generateValidationSchema
305
- * @inputSpec operation: 'sanitize' | 'format'
306
- * @outputSpec Record<string, any> — same keys, transformed values
307
- * @sideEffects none
308
- * @calledBy Custom code in v2-custom/ that needs bulk field transformation
309
- * @calls sanitizeFieldData | formatFieldData (per field)
310
- * @testUnit tests/unit/schema-utils.test.ts — 'transformRecordData' describe block
311
- *
312
- * @example
313
- * ```ts
314
- * const schema = generateValidationSchema(type.design_schema)
315
- * const sanitized = transformRecordData(body.data, schema, 'sanitize')
316
- * const formatted = transformRecordData(record.data, schema, 'format', ctx)
317
- * ```
318
- */
250
+ // CIL[END chunk_id="schemautils_formatfielddata_1784020784"]
251
+ // CIL[BEGIN chunk_id="schemautils_transformrecorddata_1784020784" type="function" identity="transformRecordData" parent="none"]
252
+ // CIL[DOC-MACRO purpose="Transform record data using validation schema"]
253
+ // CIL[DOC-RESPONSIBILITY responsibility="Apply sanitization or formatting to all record fields"]
254
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
255
+ // CIL[DOC-CONTRACT inputs="none" outputs="transformed data" errors="none" side_effects="none"]
256
+ // CIL[DOC-MICRO logic_summary="Iterates through fields and applies appropriate transformation" notable_decisions="[]"]
257
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
258
+ // CIL[DOC-STABILITY stability="stable"]
259
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
260
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
261
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
262
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
263
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
264
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
319
265
  export function transformRecordData(
320
266
  data: Record<string, any>,
321
267
  validationSchema: ValidationSchema,
@@ -328,7 +274,6 @@ export function transformRecordData(
328
274
  const fieldValidation = validationSchema.fields[fieldName]
329
275
 
330
276
  if (!fieldValidation) {
331
- // No validation schema for this field, pass through as-is
332
277
  transformed[fieldName] = fieldValue
333
278
  continue
334
279
  }
@@ -350,22 +295,26 @@ export function transformRecordData(
350
295
 
351
296
  return transformed
352
297
  }
353
-
354
- // ─── SANITIZE HELPERS ────────────────────────────────────────────────────────────
355
-
356
- /**
357
- * Trims, strips control characters, HTML-escapes, and applies minLength/
358
- * maxLength/pattern constraints. Throws on minLength/pattern violation;
359
- * silently truncates on maxLength.
360
- * @throws Error on minLength or pattern violation
361
- */
298
+ // CIL[END chunk_id="schemautils_transformrecorddata_1784020784"]
299
+ // CIL[BEGIN chunk_id="schemautils_sanitizetext_1784020784" type="function" identity="sanitizeText" parent="none"]
300
+ // CIL[DOC-MACRO purpose="Sanitize text field data with HTML encoding"]
301
+ // CIL[DOC-RESPONSIBILITY responsibility="Clean text and apply validation rules"]
302
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
303
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized string" errors="validation errors" side_effects="none"]
304
+ // CIL[DOC-MICRO logic_summary="Removes control chars, HTML encodes, validates length/pattern" notable_decisions="[]"]
305
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
306
+ // CIL[DOC-STABILITY stability="stable"]
307
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
308
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
309
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
310
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
311
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
312
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
362
313
  function sanitizeText(data: any, validation?: any): string {
363
314
  let text = String(data).trim()
364
315
 
365
- // Remove control characters except newlines and tabs
366
316
  text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
367
317
 
368
- // Escape HTML entities
369
318
  text = text
370
319
  .replace(/&/g, '&amp;')
371
320
  .replace(/</g, '&lt;')
@@ -373,7 +322,6 @@ function sanitizeText(data: any, validation?: any): string {
373
322
  .replace(/"/g, '&quot;')
374
323
  .replace(/'/g, '&#x27;')
375
324
 
376
- // Apply length constraints
377
325
  if (validation?.minLength && text.length < validation.minLength) {
378
326
  throw new Error(`Text must be at least ${validation.minLength} characters`)
379
327
  }
@@ -381,7 +329,6 @@ function sanitizeText(data: any, validation?: any): string {
381
329
  text = text.substring(0, validation.maxLength)
382
330
  }
383
331
 
384
- // Apply pattern validation
385
332
  if (validation?.pattern) {
386
333
  const regex = new RegExp(validation.pattern)
387
334
  if (!regex.test(text)) {
@@ -391,19 +338,26 @@ function sanitizeText(data: any, validation?: any): string {
391
338
 
392
339
  return text
393
340
  }
394
-
395
- /**
396
- * Same as sanitizeText but preserves newlines. Strips control chars,
397
- * HTML-escapes, applies minLength/maxLength.
398
- * @throws Error on minLength violation
399
- */
341
+ // CIL[END chunk_id="schemautils_sanitizetext_1784020784"]
342
+ // CIL[BEGIN chunk_id="schemautils_sanitizetextarea_1784020784" type="function" identity="sanitizeTextarea" parent="none"]
343
+ // CIL[DOC-MACRO purpose="Sanitize textarea field data"]
344
+ // CIL[DOC-RESPONSIBILITY responsibility="Clean textarea content and validate length"]
345
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
346
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized string" errors="length validation errors" side_effects="none"]
347
+ // CIL[DOC-MICRO logic_summary="Removes control chars, HTML encodes, checks length limits" notable_decisions="[]"]
348
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
349
+ // CIL[DOC-STABILITY stability="stable"]
350
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
351
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
352
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
353
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
354
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
355
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
400
356
  function sanitizeTextarea(data: any, validation?: any): string {
401
357
  let text = String(data).trim()
402
358
 
403
- // Remove control characters except newlines and tabs
404
359
  text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
405
360
 
406
- // Escape HTML entities but preserve line breaks
407
361
  text = text
408
362
  .replace(/&/g, '&amp;')
409
363
  .replace(/</g, '&lt;')
@@ -411,7 +365,6 @@ function sanitizeTextarea(data: any, validation?: any): string {
411
365
  .replace(/"/g, '&quot;')
412
366
  .replace(/'/g, '&#x27;')
413
367
 
414
- // Apply length constraints
415
368
  if (validation?.minLength && text.length < validation.minLength) {
416
369
  throw new Error(`Text must be at least ${validation.minLength} characters`)
417
370
  }
@@ -421,17 +374,24 @@ function sanitizeTextarea(data: any, validation?: any): string {
421
374
 
422
375
  return text
423
376
  }
424
-
425
- /**
426
- * Allowlist-based HTML sanitizer for rich text. Strips all tags not in the
427
- * allowed set (`p br strong em u ol ul li a h1-h6`), removes script tags and
428
- * `on*` event attributes, applies minLength/maxLength.
429
- * @throws Error on minLength violation
430
- */
377
+ // CIL[END chunk_id="schemautils_sanitizetextarea_1784020784"]
378
+ // CIL[BEGIN chunk_id="schemautils_sanitizerichtext_1784020784" type="function" identity="sanitizeRichText" parent="none"]
379
+ // CIL[DOC-MACRO purpose="Sanitize rich text HTML content"]
380
+ // CIL[DOC-RESPONSIBILITY responsibility="Filter HTML tags and remove dangerous content"]
381
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
382
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized HTML" errors="length validation errors" side_effects="none"]
383
+ // CIL[DOC-MICRO logic_summary="Allows safe tags, removes scripts, strips event handlers" notable_decisions="[]"]
384
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
385
+ // CIL[DOC-STABILITY stability="stable"]
386
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
387
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
388
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
389
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
390
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
391
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
431
392
  function sanitizeRichText(data: any, validation?: any): string {
432
393
  let html = String(data).trim()
433
394
 
434
- // Basic HTML sanitization - allow only safe tags
435
395
  const allowedTags = ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
436
396
  const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g
437
397
 
@@ -442,11 +402,9 @@ function sanitizeRichText(data: any, validation?: any): string {
442
402
  return ''
443
403
  })
444
404
 
445
- // Remove script tags and on* attributes
446
405
  html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
447
406
  html = html.replace(/on\w+\s*=/gi, '')
448
407
 
449
- // Apply length constraints
450
408
  if (validation?.minLength && html.length < validation.minLength) {
451
409
  throw new Error(`Content must be at least ${validation.minLength} characters`)
452
410
  }
@@ -456,15 +414,24 @@ function sanitizeRichText(data: any, validation?: any): string {
456
414
 
457
415
  return html
458
416
  }
459
-
460
- /**
461
- * Lowercases, trims, and validates basic `name@domain.tld` format.
462
- * @throws Error('Invalid email format') on invalid input
463
- */
417
+ // CIL[END chunk_id="schemautils_sanitizerichtext_1784020784"]
418
+ // CIL[BEGIN chunk_id="schemautils_sanitizeemail_1784020784" type="function" identity="sanitizeEmail" parent="none"]
419
+ // CIL[DOC-MACRO purpose="Validate and sanitize email addresses"]
420
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure email format is valid"]
421
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
422
+ // CIL[DOC-CONTRACT inputs="none" outputs="normalized email" errors="invalid format error" side_effects="none"]
423
+ // CIL[DOC-MICRO logic_summary="Lowercases, trims, validates with regex pattern" notable_decisions="[]"]
424
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
425
+ // CIL[DOC-STABILITY stability="stable"]
426
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
427
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
428
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
429
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
430
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
431
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
464
432
  function sanitizeEmail(data: any, validation?: any): string {
465
433
  let email = String(data).toLowerCase().trim()
466
434
 
467
- // Basic email validation
468
435
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
469
436
  if (!emailRegex.test(email)) {
470
437
  throw new Error('Invalid email format')
@@ -472,18 +439,26 @@ function sanitizeEmail(data: any, validation?: any): string {
472
439
 
473
440
  return email
474
441
  }
475
-
476
- /**
477
- * Strips all non-digit/non-`+` characters. Applies optional pattern validation.
478
- * @throws Error on pattern mismatch
479
- */
442
+ // CIL[END chunk_id="schemautils_sanitizeemail_1784020784"]
443
+ // CIL[BEGIN chunk_id="schemautils_sanitizephone_1784020784" type="function" identity="sanitizePhone" parent="none"]
444
+ // CIL[DOC-MACRO purpose="Sanitize phone number format"]
445
+ // CIL[DOC-RESPONSIBILITY responsibility="Clean and validate phone numbers"]
446
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
447
+ // CIL[DOC-CONTRACT inputs="none" outputs="cleaned phone" errors="format validation errors" side_effects="none"]
448
+ // CIL[DOC-MICRO logic_summary="Removes non-digits, validates against pattern if provided" notable_decisions="[]"]
449
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
450
+ // CIL[DOC-STABILITY stability="stable"]
451
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
452
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
453
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
454
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
455
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
456
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
480
457
  function sanitizePhone(data: any, validation?: any): string {
481
458
  let phone = String(data).trim()
482
459
 
483
- // Remove all non-digit characters except +
484
460
  phone = phone.replace(/[^\d+]/g, '')
485
461
 
486
- // Apply pattern validation if specified
487
462
  if (validation?.pattern) {
488
463
  const regex = new RegExp(validation.pattern)
489
464
  if (!regex.test(phone)) {
@@ -493,18 +468,26 @@ function sanitizePhone(data: any, validation?: any): string {
493
468
 
494
469
  return phone
495
470
  }
496
-
497
- /**
498
- * Parses via `new URL()` and validates `http:` or `https:` protocol.
499
- * @throws Error on invalid URL or disallowed protocol
500
- */
471
+ // CIL[END chunk_id="schemautils_sanitizephone_1784020784"]
472
+ // CIL[BEGIN chunk_id="schemautils_sanitizeurl_1784020784" type="function" identity="sanitizeUrl" parent="none"]
473
+ // CIL[DOC-MACRO purpose="Validate and sanitize URLs"]
474
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure URL format and protocol are valid"]
475
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
476
+ // CIL[DOC-CONTRACT inputs="none" outputs="normalized URL" errors="invalid URL error" side_effects="none"]
477
+ // CIL[DOC-MICRO logic_summary="Validates URL format, restricts to HTTP/HTTPS protocols" notable_decisions="[]"]
478
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
479
+ // CIL[DOC-STABILITY stability="stable"]
480
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
481
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
482
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
483
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
484
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
485
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
501
486
  function sanitizeUrl(data: any, validation?: any): string {
502
487
  let url = String(data).trim()
503
488
 
504
- // Basic URL validation
505
489
  try {
506
490
  const urlObj = new URL(url)
507
- // Only allow http/https protocols
508
491
  if (!['http:', 'https:'].includes(urlObj.protocol)) {
509
492
  throw new Error('Only HTTP and HTTPS URLs are allowed')
510
493
  }
@@ -513,12 +496,21 @@ function sanitizeUrl(data: any, validation?: any): string {
513
496
  throw new Error('Invalid URL format')
514
497
  }
515
498
  }
516
-
517
- /**
518
- * Coerces to Number, applies min/max constraints, rounds down to nearest
519
- * step if `validation.step` is set.
520
- * @throws Error on NaN or out-of-range
521
- */
499
+ // CIL[END chunk_id="schemautils_sanitizeurl_1784020784"]
500
+ // CIL[BEGIN chunk_id="schemautils_sanitizenumber_1784020784" type="function" identity="sanitizeNumber" parent="none"]
501
+ // CIL[DOC-MACRO purpose="Validate and sanitize numeric values"]
502
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure number validity and range constraints"]
503
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
504
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated number" errors="range/validation errors" side_effects="none"]
505
+ // CIL[DOC-MICRO logic_summary="Validates NaN, checks min/max, applies step rounding" notable_decisions="[]"]
506
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
507
+ // CIL[DOC-STABILITY stability="stable"]
508
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
509
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
510
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
511
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
512
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
513
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
522
514
  function sanitizeNumber(data: any, validation?: any): number {
523
515
  let num = Number(data)
524
516
 
@@ -526,7 +518,6 @@ function sanitizeNumber(data: any, validation?: any): number {
526
518
  throw new Error('Invalid number')
527
519
  }
528
520
 
529
- // Apply min/max constraints
530
521
  if (validation?.min !== undefined && num < validation.min) {
531
522
  throw new Error(`Number must be at least ${validation.min}`)
532
523
  }
@@ -534,21 +525,30 @@ function sanitizeNumber(data: any, validation?: any): number {
534
525
  throw new Error(`Number must be at most ${validation.max}`)
535
526
  }
536
527
 
537
- // Apply step constraint
538
528
  if (validation?.step) {
539
529
  const remainder = num % validation.step
540
530
  if (remainder !== 0) {
541
- num = num - remainder // Round down to nearest step
531
+ num = num - remainder
542
532
  }
543
533
  }
544
534
 
545
535
  return num
546
536
  }
547
-
548
- /**
549
- * Coerces to Number, rounds to 2 decimal places, applies min/max.
550
- * @throws Error on NaN or out-of-range
551
- */
537
+ // CIL[END chunk_id="schemautils_sanitizenumber_1784020784"]
538
+ // CIL[BEGIN chunk_id="schemautils_sanitizeurrency_1784020784" type="function" identity="sanitizeCurrency" parent="none"]
539
+ // CIL[DOC-MACRO purpose="Sanitize currency amounts with rounding"]
540
+ // CIL[DOC-RESPONSIBILITY responsibility="Validate and round currency values"]
541
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
542
+ // CIL[DOC-CONTRACT inputs="none" outputs="rounded currency" errors="range/validation errors" side_effects="none"]
543
+ // CIL[DOC-MICRO logic_summary="Rounds to 2 decimal places, validates range constraints" notable_decisions="[]"]
544
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
545
+ // CIL[DOC-STABILITY stability="stable"]
546
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
547
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
548
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
549
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
550
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
551
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
552
552
  function sanitizeCurrency(data: any, validation?: any): number {
553
553
  let num = Number(data)
554
554
 
@@ -556,10 +556,8 @@ function sanitizeCurrency(data: any, validation?: any): number {
556
556
  throw new Error('Invalid currency amount')
557
557
  }
558
558
 
559
- // Round to 2 decimal places for currency
560
559
  num = Math.round(num * 100) / 100
561
560
 
562
- // Apply min/max constraints
563
561
  if (validation?.min !== undefined && num < validation.min) {
564
562
  throw new Error(`Amount must be at least ${validation.min}`)
565
563
  }
@@ -569,30 +567,49 @@ function sanitizeCurrency(data: any, validation?: any): number {
569
567
 
570
568
  return num
571
569
  }
572
-
573
- /** Delegates to `sanitizeNumber`. @throws same as sanitizeNumber */
570
+ // CIL[END chunk_id="schemautils_sanitizeurrency_1784020784"]
571
+ // CIL[BEGIN chunk_id="schemautils_sanitizerange_1784020784" type="function" identity="sanitizeRange" parent="none"]
572
+ // CIL[DOC-MACRO purpose="Sanitize range values"]
573
+ // CIL[DOC-RESPONSIBILITY responsibility="Delegate range sanitization to number sanitizer"]
574
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
575
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized number" errors="number validation errors" side_effects="none"]
576
+ // CIL[DOC-MICRO logic_summary="Simple wrapper around sanitizeNumber function" notable_decisions="[]"]
577
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
578
+ // CIL[DOC-STABILITY stability="stable"]
579
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
580
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
581
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
582
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
583
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
584
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
574
585
  function sanitizeRange(data: any, validation?: any): number {
575
586
  return sanitizeNumber(data, validation)
576
587
  }
577
-
578
- /**
579
- * Parses via `new Date()`, returns ISO date string (`YYYY-MM-DD`). Applies
580
- * min/max date constraints.
581
- * @throws Error on invalid date or out-of-range
582
- */
588
+ // CIL[END chunk_id="schemautils_sanitizerange_1784020784"]
589
+ // CIL[BEGIN chunk_id="schemautils_sanitizedate_1784020784" type="function" identity="sanitizeDate" parent="none"]
590
+ // CIL[DOC-MACRO purpose="Validate and format date strings"]
591
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure valid date format and constraints"]
592
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
593
+ // CIL[DOC-CONTRACT inputs="none" outputs="ISO date string" errors="date validation errors" side_effects="none"]
594
+ // CIL[DOC-MICRO logic_summary="Parses date, validates range, returns ISO format" notable_decisions="[]"]
595
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
596
+ // CIL[DOC-STABILITY stability="stable"]
597
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
598
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
599
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
600
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
601
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
602
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
583
603
  function sanitizeDate(data: any, validation?: any): string {
584
604
  let dateStr = String(data).trim()
585
605
 
586
- // Try to parse as ISO date
587
606
  const date = new Date(dateStr)
588
607
  if (isNaN(date.getTime())) {
589
608
  throw new Error('Invalid date format')
590
609
  }
591
610
 
592
- // Return as ISO date string
593
611
  const isoDate = date.toISOString().split('T')[0]
594
612
 
595
- // Apply min/max constraints
596
613
  if (validation?.min) {
597
614
  const minDate = new Date(validation.min)
598
615
  if (date < minDate) {
@@ -608,25 +625,31 @@ function sanitizeDate(data: any, validation?: any): string {
608
625
 
609
626
  return isoDate
610
627
  }
611
-
612
- /**
613
- * Parses via `new Date()`, returns full ISO datetime string. Applies
614
- * min/max datetime constraints.
615
- * @throws Error on invalid datetime or out-of-range
616
- */
628
+ // CIL[END chunk_id="schemautils_sanitizedate_1784020784"]
629
+ // CIL[BEGIN chunk_id="schemautils_sanitizedatetime_1784020784" type="function" identity="sanitizeDatetime" parent="none"]
630
+ // CIL[DOC-MACRO purpose="Validate and format datetime strings"]
631
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure valid datetime format and constraints"]
632
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
633
+ // CIL[DOC-CONTRACT inputs="none" outputs="ISO datetime string" errors="datetime validation errors" side_effects="none"]
634
+ // CIL[DOC-MICRO logic_summary="Parses datetime, validates range, returns ISO format" notable_decisions="[]"]
635
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
636
+ // CIL[DOC-STABILITY stability="stable"]
637
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
638
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
639
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
640
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
641
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
642
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
617
643
  function sanitizeDatetime(data: any, validation?: any): string {
618
644
  let dateStr = String(data).trim()
619
645
 
620
- // Try to parse as ISO datetime
621
646
  const date = new Date(dateStr)
622
647
  if (isNaN(date.getTime())) {
623
648
  throw new Error('Invalid datetime format')
624
649
  }
625
650
 
626
- // Return as ISO datetime string
627
651
  const isoDatetime = date.toISOString()
628
652
 
629
- // Apply min/max constraints
630
653
  if (validation?.min) {
631
654
  const minDate = new Date(validation.min)
632
655
  if (date < minDate) {
@@ -642,12 +665,21 @@ function sanitizeDatetime(data: any, validation?: any): string {
642
665
 
643
666
  return isoDatetime
644
667
  }
645
-
646
- /**
647
- * Accepts `true/false` booleans or truthy/falsy strings
648
- * (`'true','1','yes','on'` / `'false','0','no','off'`).
649
- * @throws Error on unrecognised value
650
- */
668
+ // CIL[END chunk_id="schemautils_sanitizedatetime_1784020784"]
669
+ // CIL[BEGIN chunk_id="schemautils_sanitizeboolean_1784020784" type="function" identity="sanitizeBoolean" parent="none"]
670
+ // CIL[DOC-MACRO purpose="Convert and sanitize boolean values"]
671
+ // CIL[DOC-RESPONSIBILITY responsibility="Normalize various boolean representations"]
672
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
673
+ // CIL[DOC-CONTRACT inputs="none" outputs="boolean value" errors="invalid boolean error" side_effects="none"]
674
+ // CIL[DOC-MICRO logic_summary="Handles boolean, string, number representations" notable_decisions="[]"]
675
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
676
+ // CIL[DOC-STABILITY stability="stable"]
677
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
678
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
679
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
680
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
681
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
682
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
651
683
  function sanitizeBoolean(data: any, validation?: any): boolean {
652
684
  if (typeof data === 'boolean') {
653
685
  return data
@@ -662,22 +694,43 @@ function sanitizeBoolean(data: any, validation?: any): boolean {
662
694
 
663
695
  throw new Error('Invalid boolean value')
664
696
  }
665
-
666
- /** Delegates to `sanitizeBoolean`. */
697
+ // CIL[END chunk_id="schemautils_sanitizeboolean_1784020784"]
698
+ // CIL[BEGIN chunk_id="schemautils_sanitizecheckbox_1784020784" type="function" identity="sanitizeCheckbox" parent="none"]
699
+ // CIL[DOC-MACRO purpose="Sanitize checkbox boolean values"]
700
+ // CIL[DOC-RESPONSIBILITY responsibility="Delegate checkbox sanitization to boolean sanitizer"]
701
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
702
+ // CIL[DOC-CONTRACT inputs="none" outputs="boolean value" errors="boolean validation errors" side_effects="none"]
703
+ // CIL[DOC-MICRO logic_summary="Simple wrapper around sanitizeBoolean function" notable_decisions="[]"]
704
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
705
+ // CIL[DOC-STABILITY stability="stable"]
706
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
707
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
708
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
709
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
710
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
711
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
667
712
  function sanitizeCheckbox(data: any, validation?: any): boolean {
668
713
  return sanitizeBoolean(data, validation)
669
714
  }
670
-
671
- /**
672
- * Validates the value against `validation.options` (string[]).
673
- * @throws Error('Invalid option selected') if value not in allowed list
674
- */
715
+ // CIL[END chunk_id="schemautils_sanitizecheckbox_1784020784"]
716
+ // CIL[BEGIN chunk_id="schemautils_sanitizeselect_1784020784" type="function" identity="sanitizeSelect" parent="none"]
717
+ // CIL[DOC-MACRO purpose="Validate select field options"]
718
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure selected value is in allowed options"]
719
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
720
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated option" errors="invalid option error" side_effects="none"]
721
+ // CIL[DOC-MICRO logic_summary="Trims value, checks against allowed options array" notable_decisions="[]"]
722
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
723
+ // CIL[DOC-STABILITY stability="stable"]
724
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
725
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
726
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
727
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
728
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
729
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
675
730
  function sanitizeSelect(data: any, validation?: any): string {
676
731
  let value = String(data).trim()
677
732
 
678
- // Validate against allowed options
679
733
  if (validation?.options) {
680
- // Options are now just an array of strings
681
734
  const allowedValues = Array.isArray(validation.options) ? validation.options : []
682
735
  if (!allowedValues.includes(value)) {
683
736
  throw new Error('Invalid option selected')
@@ -686,12 +739,21 @@ function sanitizeSelect(data: any, validation?: any): string {
686
739
 
687
740
  return value
688
741
  }
689
-
690
- /**
691
- * Accepts array or comma-separated string. Deduplicates. Validates each
692
- * value against `validation.options`. Truncates to `validation.max`.
693
- * @throws Error on invalid option or non-array/string input
694
- */
742
+ // CIL[END chunk_id="schemautils_sanitizeselect_1784020784"]
743
+ // CIL[BEGIN chunk_id="schemautils_sanitizemultiselect_1784020784" type="function" identity="sanitizeMultiselect" parent="none"]
744
+ // CIL[DOC-MACRO purpose="Validate multiselect array values"]
745
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle array or comma-separated multiselect data"]
746
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
747
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated array" errors="invalid option errors" side_effects="none"]
748
+ // CIL[DOC-MICRO logic_summary="Converts to array, removes duplicates, validates options" notable_decisions="[]"]
749
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
750
+ // CIL[DOC-STABILITY stability="stable"]
751
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
752
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
753
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
754
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
755
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
756
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
695
757
  function sanitizeMultiselect(data: any, validation?: any): string[] {
696
758
  let values: string[]
697
759
 
@@ -703,12 +765,9 @@ function sanitizeMultiselect(data: any, validation?: any): string[] {
703
765
  throw new Error('Multiselect must be an array or comma-separated string')
704
766
  }
705
767
 
706
- // Remove duplicates
707
768
  values = [...new Set(values)]
708
769
 
709
- // Validate against allowed options
710
770
  if (validation?.options) {
711
- // Options are now just an array of strings
712
771
  const allowedValues = Array.isArray(validation.options) ? validation.options : []
713
772
  for (const value of values) {
714
773
  if (!allowedValues.includes(value)) {
@@ -717,89 +776,129 @@ function sanitizeMultiselect(data: any, validation?: any): string[] {
717
776
  }
718
777
  }
719
778
 
720
- // Apply max selection count
721
779
  if (validation?.max && values.length > validation.max) {
722
780
  values = values.slice(0, validation.max)
723
781
  }
724
782
 
725
783
  return values
726
784
  }
727
-
728
- /** Delegates to `sanitizeSelect`. */
785
+ // CIL[END chunk_id="schemautils_sanitizemultiselect_1784020784"]
786
+ // CIL[BEGIN chunk_id="schemautils_sanitizeradio_1784020784" type="function" identity="sanitizeRadio" parent="none"]
787
+ // CIL[DOC-MACRO purpose="Validate radio button selections"]
788
+ // CIL[DOC-RESPONSIBILITY responsibility="Delegate radio sanitization to select sanitizer"]
789
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
790
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated option" errors="invalid option error" side_effects="none"]
791
+ // CIL[DOC-MICRO logic_summary="Simple wrapper around sanitizeSelect function" notable_decisions="[]"]
792
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
793
+ // CIL[DOC-STABILITY stability="stable"]
794
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
795
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
796
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
797
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
798
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
799
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
729
800
  function sanitizeRadio(data: any, validation?: any): string {
730
801
  return sanitizeSelect(data, validation)
731
802
  }
732
-
733
- /**
734
- * Validates `#RGB` or `#RRGGBB` hex format. Normalizes 3-digit to 6-digit.
735
- * Returns uppercase. @throws Error on invalid hex format
736
- */
803
+ // CIL[END chunk_id="schemautils_sanitizeradio_1784020784"]
804
+ // CIL[BEGIN chunk_id="schemautils_sanitizeolor_1784020784" type="function" identity="sanitizeColor" parent="none"]
805
+ // CIL[DOC-MACRO purpose="Validate and format hex color codes"]
806
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure proper hex color format"]
807
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
808
+ // CIL[DOC-CONTRACT inputs="none" outputs="normalized hex color" errors="invalid color format error" side_effects="none"]
809
+ // CIL[DOC-MICRO logic_summary="Validates hex format, converts 3-digit to 6-digit, uppercases" notable_decisions="[]"]
810
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
811
+ // CIL[DOC-STABILITY stability="stable"]
812
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
813
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
814
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
815
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
816
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
817
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
737
818
  function sanitizeColor(data: any, validation?: any): string {
738
819
  let color = String(data).trim()
739
820
 
740
- // Validate hex color format
741
821
  const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
742
822
  if (!hexRegex.test(color)) {
743
823
  throw new Error('Invalid color format. Use #RRGGBB or #RGB format')
744
824
  }
745
825
 
746
- // Normalize to 6-digit hex
747
826
  if (color.length === 4) {
748
827
  color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
749
828
  }
750
829
 
751
830
  return color.toUpperCase()
752
831
  }
753
-
754
- /**
755
- * Validates `data.name` presence, optional maxSize and allowedTypes.
756
- * Sanitizes filename to `[a-zA-Z0-9.-_]` characters only.
757
- * @throws Error on invalid file data, size, or type
758
- */
832
+ // CIL[END chunk_id="schemautils_sanitizeolor_1784020784"]
833
+ // CIL[BEGIN chunk_id="schemautils_sanitizeile_1784020784" type="function" identity="sanitizeFile" parent="none"]
834
+ // CIL[DOC-MACRO purpose="Validate file objects and metadata"]
835
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure file data validity and constraints"]
836
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
837
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated file object" errors="file validation errors" side_effects="none"]
838
+ // CIL[DOC-MICRO logic_summary="Checks object structure, size, type, sanitizes filename" notable_decisions="[]"]
839
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
840
+ // CIL[DOC-STABILITY stability="stable"]
841
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
842
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
843
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
844
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
845
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
846
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
759
847
  function sanitizeFile(data: any, validation?: any): any {
760
- // Basic file validation - would need more sophisticated handling in practice
761
848
  if (typeof data !== 'object' || !data.name) {
762
849
  throw new Error('Invalid file data')
763
850
  }
764
851
 
765
- // Validate file size
766
852
  if (validation?.maxSize && data.size > validation.maxSize) {
767
853
  throw new Error(`File size exceeds maximum of ${validation.maxSize} bytes`)
768
854
  }
769
855
 
770
- // Validate file type
771
856
  if (validation?.allowedTypes && !validation.allowedTypes.includes(data.type)) {
772
857
  throw new Error(`File type ${data.type} is not allowed`)
773
858
  }
774
859
 
775
- // Sanitize filename
776
860
  data.name = data.name.replace(/[^a-zA-Z0-9.-]/g, '_')
777
861
 
778
862
  return data
779
863
  }
780
-
781
- /**
782
- * Delegates to `sanitizeFile`. Image dimension validation is a stub
783
- * (noted for future implementation).
784
- * @throws same as sanitizeFile
785
- */
864
+ // CIL[END chunk_id="schemautils_sanitizeile_1784020784"]
865
+ // CIL[BEGIN chunk_id="schemautils_sanitizeimage_1784020784" type="function" identity="sanitizeImage" parent="none"]
866
+ // CIL[DOC-MACRO purpose="Validate image files with dimensions"]
867
+ // CIL[DOC-RESPONSIBILITY responsibility="Extend file sanitization for image-specific validation"]
868
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
869
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated image object" errors="image validation errors" side_effects="none"]
870
+ // CIL[DOC-MICRO logic_summary="Uses file sanitizer, placeholder for dimension validation" notable_decisions="[]"]
871
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
872
+ // CIL[DOC-STABILITY stability="stable"]
873
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
874
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
875
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
876
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
877
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
878
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
786
879
  function sanitizeImage(data: any, validation?: any): any {
787
880
  const file = sanitizeFile(data, validation)
788
881
 
789
- // Additional image-specific validation
790
882
  if (validation?.maxWidth || validation?.maxHeight) {
791
- // Would need to actually load and check image dimensions
792
- // For now, just pass through
793
883
  }
794
884
 
795
885
  return file
796
886
  }
797
-
798
- /**
799
- * Parses JSON strings. Rejects payloads containing `'function'`, `'eval'`,
800
- * or `'script'` strings as a basic code-injection guard.
801
- * @throws Error on invalid JSON or dangerous content
802
- */
887
+ // CIL[END chunk_id="schemautils_sanitizeimage_1784020784"]
888
+ // CIL[BEGIN chunk_id="schemautils_sanitizejson_1784020784" type="function" identity="sanitizeJson" parent="none"]
889
+ // CIL[DOC-MACRO purpose="Parse and sanitize JSON data"]
890
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure JSON validity and remove dangerous content"]
891
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
892
+ // CIL[DOC-CONTRACT inputs="none" outputs="parsed JSON object" errors="JSON parsing/security errors" side_effects="none"]
893
+ // CIL[DOC-MICRO logic_summary="Parses string JSON, checks for dangerous keywords" notable_decisions="[]"]
894
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
895
+ // CIL[DOC-STABILITY stability="stable"]
896
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
897
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
898
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
899
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
900
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
901
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
803
902
  function sanitizeJson(data: any, validation?: any): any {
804
903
  if (typeof data === 'string') {
805
904
  try {
@@ -809,7 +908,6 @@ function sanitizeJson(data: any, validation?: any): any {
809
908
  }
810
909
  }
811
910
 
812
- // Basic security check - prevent code injection
813
911
  const jsonStr = JSON.stringify(data)
814
912
  if (jsonStr.includes('function') || jsonStr.includes('eval') || jsonStr.includes('script')) {
815
913
  throw new Error('JSON contains potentially dangerous content')
@@ -817,37 +915,51 @@ function sanitizeJson(data: any, validation?: any): any {
817
915
 
818
916
  return data
819
917
  }
820
-
821
- /**
822
- * Validates UUID v1–v5 format. DB-level FK constraints enforce existence.
823
- * @throws Error('Invalid reference format') on non-UUID input
824
- */
918
+ // CIL[END chunk_id="schemautils_sanitizejson_1784020784"]
919
+ // CIL[BEGIN chunk_id="schemautils_sanitizereference_1784020784" type="function" identity="sanitizeReference" parent="none"]
920
+ // CIL[DOC-MACRO purpose="Validate UUID reference format"]
921
+ // CIL[DOC-RESPONSIBILITY responsibility="Ensure reference follows UUID pattern"]
922
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
923
+ // CIL[DOC-CONTRACT inputs="none" outputs="validated UUID string" errors="invalid UUID error" side_effects="none"]
924
+ // CIL[DOC-MICRO logic_summary="Validates against UUID regex pattern, trims whitespace" notable_decisions="[]"]
925
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
926
+ // CIL[DOC-STABILITY stability="stable"]
927
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
928
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
929
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
930
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
931
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
932
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
825
933
  function sanitizeReference(data: any, validation?: any): string {
826
934
  let ref = String(data).trim()
827
935
 
828
- // Validate UUID format
829
936
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
830
937
  if (!uuidRegex.test(ref)) {
831
938
  throw new Error('Invalid reference format')
832
939
  }
833
940
 
834
- // Would need to check existence in referenced table
835
- // For now, just validate format
836
-
837
941
  return ref
838
942
  }
839
-
840
- /**
841
- * Sanitizes each string field of an address object via `sanitizeText`.
842
- * Passes non-string fields through unchanged.
843
- * @throws Error if input is not an object
844
- */
943
+ // CIL[END chunk_id="schemautils_sanitizereference_1784020784"]
944
+ // CIL[BEGIN chunk_id="schemautils_sanitizeaddress_1784020784" type="function" identity="sanitizeAddress" parent="none"]
945
+ // CIL[DOC-MACRO purpose="Sanitize address object data"]
946
+ // CIL[DOC-RESPONSIBILITY responsibility="Clean address fields and validate structure"]
947
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
948
+ // CIL[DOC-CONTRACT inputs="none" outputs="sanitized address object" errors="invalid address error" side_effects="none"]
949
+ // CIL[DOC-MICRO logic_summary="Validates object type, sanitizes string fields with text sanitizer" notable_decisions="[]"]
950
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
951
+ // CIL[DOC-STABILITY stability="stable"]
952
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
953
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
954
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
955
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
956
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
957
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
845
958
  function sanitizeAddress(data: any, validation?: any): any {
846
959
  if (typeof data !== 'object' || data === null) {
847
960
  throw new Error('Address must be an object')
848
961
  }
849
962
 
850
- // Sanitize each address component
851
963
  const sanitized: any = {}
852
964
  for (const [key, value] of Object.entries(data)) {
853
965
  if (typeof value === 'string') {
@@ -859,15 +971,39 @@ function sanitizeAddress(data: any, validation?: any): any {
859
971
 
860
972
  return sanitized
861
973
  }
862
-
863
- // ─── FORMAT HELPERS ────────────────────────────────────────────────────────────
864
-
865
- /** Formats an object as a 2-space indented JSON string. */
974
+ // CIL[END chunk_id="schemautils_sanitizeaddress_1784020784"]
975
+ // CIL[BEGIN chunk_id="schemautils_formatjson_1784020784" type="function" identity="formatJson" parent="none"]
976
+ // CIL[DOC-MACRO purpose="Format JSON with pretty printing"]
977
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert JSON to readable string format"]
978
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
979
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted JSON string" errors="none" side_effects="none"]
980
+ // CIL[DOC-MICRO logic_summary="Uses JSON.stringify with 2-space indentation" notable_decisions="[]"]
981
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
982
+ // CIL[DOC-STABILITY stability="stable"]
983
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
984
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
985
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
986
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
987
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
988
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
866
989
  function formatJson(data: any): string {
867
990
  return JSON.stringify(data, null, 2)
868
991
  }
869
-
870
- /** Formats an ISO date string as locale date ('January 15, 2024'). Returns raw data on invalid input. */
992
+ // CIL[END chunk_id="schemautils_formatjson_1784020784"]
993
+ // CIL[BEGIN chunk_id="schemautils_formatdate_1784020784" type="function" identity="formatDate" parent="none"]
994
+ // CIL[DOC-MACRO purpose="Format dates for display"]
995
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert date to human-readable format"]
996
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
997
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted date string" errors="none" side_effects="none"]
998
+ // CIL[DOC-MICRO logic_summary="Uses toLocaleDateString with long month format" notable_decisions="[]"]
999
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1000
+ // CIL[DOC-STABILITY stability="stable"]
1001
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1002
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1003
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1004
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1005
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1006
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
871
1007
  function formatDate(data: string): string {
872
1008
  const date = new Date(data)
873
1009
  if (isNaN(date.getTime())) {
@@ -880,8 +1016,21 @@ function formatDate(data: string): string {
880
1016
  day: 'numeric'
881
1017
  })
882
1018
  }
883
-
884
- /** Formats an ISO datetime string as locale date+time ('January 15, 2024, 02:30 PM'). Returns raw on invalid. */
1019
+ // CIL[END chunk_id="schemautils_formatdate_1784020784"]
1020
+ // CIL[BEGIN chunk_id="schemautils_formatdatetime_1784020784" type="function" identity="formatDatetime" parent="none"]
1021
+ // CIL[DOC-MACRO purpose="Format datetime for display"]
1022
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert datetime to human-readable format"]
1023
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1024
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted datetime string" errors="none" side_effects="none"]
1025
+ // CIL[DOC-MICRO logic_summary="Uses toLocaleString with date and time components" notable_decisions="[]"]
1026
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1027
+ // CIL[DOC-STABILITY stability="stable"]
1028
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1029
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1030
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1031
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1032
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1033
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
885
1034
  function formatDatetime(data: string): string {
886
1035
  const date = new Date(data)
887
1036
  if (isNaN(date.getTime())) {
@@ -896,8 +1045,21 @@ function formatDatetime(data: string): string {
896
1045
  minute: '2-digit'
897
1046
  })
898
1047
  }
899
-
900
- /** Formats a number as currency using Intl.NumberFormat. Defaults to USD. context.currency_code overrides. */
1048
+ // CIL[END chunk_id="schemautils_formatdatetime_1784020784"]
1049
+ // CIL[BEGIN chunk_id="schemautils_formatcurrency_1784020784" type="function" identity="formatCurrency" parent="none"]
1050
+ // CIL[DOC-MACRO purpose="Format currency with locale"]
1051
+ // CIL[DOC-RESPONSIBILITY responsibility="Format numbers as currency strings"]
1052
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1053
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted currency string" errors="none" side_effects="none"]
1054
+ // CIL[DOC-MICRO logic_summary="Uses Intl.NumberFormat with currency style" notable_decisions="[]"]
1055
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1056
+ // CIL[DOC-STABILITY stability="stable"]
1057
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1058
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1059
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1060
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1061
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1062
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
901
1063
  function formatCurrency(data: number, context?: any): string {
902
1064
  const currency = context?.currency_code || 'USD'
903
1065
  return new Intl.NumberFormat('en-US', {
@@ -905,10 +1067,22 @@ function formatCurrency(data: number, context?: any): string {
905
1067
  currency: currency
906
1068
  }).format(data)
907
1069
  }
908
-
909
- /** Formats 10-digit US numbers as '(NXX) NXX-XXXX'. 11-digit with leading 1 as '+1 (NXX) NXX-XXXX'. Returns raw otherwise. */
1070
+ // CIL[END chunk_id="schemautils_formatcurrency_1784020784"]
1071
+ // CIL[BEGIN chunk_id="schemautils_formatphone_1784020784" type="function" identity="formatPhone" parent="none"]
1072
+ // CIL[DOC-MACRO purpose="Format phone numbers for display"]
1073
+ // CIL[DOC-RESPONSIBILITY responsibility="Format phone numbers in standard US format"]
1074
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1075
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted phone string" errors="none" side_effects="none"]
1076
+ // CIL[DOC-MICRO logic_summary="Formats 10-digit and 11-digit US phone numbers" notable_decisions="[]"]
1077
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1078
+ // CIL[DOC-STABILITY stability="stable"]
1079
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1080
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1081
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1082
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1083
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1084
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
910
1085
  function formatPhone(data: string): string {
911
- // Basic US phone formatting
912
1086
  const phone = data.replace(/\D/g, '')
913
1087
  if (phone.length === 10) {
914
1088
  return `(${phone.slice(0, 3)}) ${phone.slice(3, 6)}-${phone.slice(6)}`
@@ -918,20 +1092,57 @@ function formatPhone(data: string): string {
918
1092
 
919
1093
  return data
920
1094
  }
921
-
922
- /** Pass-through — URLs are already display-ready. */
1095
+ // CIL[END chunk_id="schemautils_formatphone_1784020784"]
1096
+ // CIL[BEGIN chunk_id="schemautils_formaturl_1784020784" type="function" identity="formatUrl" parent="none"]
1097
+ // CIL[DOC-MACRO purpose="Format URLs for display"]
1098
+ // CIL[DOC-RESPONSIBILITY responsibility="Return URL data unchanged"]
1099
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1100
+ // CIL[DOC-CONTRACT inputs="none" outputs="URL string" errors="none" side_effects="none"]
1101
+ // CIL[DOC-MICRO logic_summary="Pass-through function, no formatting applied" notable_decisions="[]"]
1102
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1103
+ // CIL[DOC-STABILITY stability="stable"]
1104
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1105
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1106
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1107
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1108
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1109
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
923
1110
  function formatUrl(data: string): string {
924
1111
  return data
925
1112
  }
926
-
927
- /** Pass-through — UUID returned as-is; display resolution requires a DB lookup (not done here). */
1113
+ // CIL[END chunk_id="schemautils_formaturl_1784020784"]
1114
+ // CIL[BEGIN chunk_id="schemautils_formatreference_1784020784" type="function" identity="formatReference" parent="none"]
1115
+ // CIL[DOC-MACRO purpose="Format references for display"]
1116
+ // CIL[DOC-RESPONSIBILITY responsibility="Return reference data unchanged"]
1117
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1118
+ // CIL[DOC-CONTRACT inputs="none" outputs="reference string" errors="none" side_effects="none"]
1119
+ // CIL[DOC-MICRO logic_summary="Pass-through function, no formatting applied" notable_decisions="[]"]
1120
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1121
+ // CIL[DOC-STABILITY stability="stable"]
1122
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1123
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1124
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1125
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1126
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1127
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
928
1128
  function formatReference(data: string, context?: any): string {
929
- // Would need to look up the referenced entity
930
- // For now, return the UUID
931
1129
  return data
932
1130
  }
933
-
934
- /** Joins address components (street, city, state, postal_code, country) into a comma-separated string. */
1131
+ // CIL[END chunk_id="schemautils_formatreference_1784020784"]
1132
+ // CIL[BEGIN chunk_id="schemautils_formataddress_1784020784" type="function" identity="formatAddress" parent="none"]
1133
+ // CIL[DOC-MACRO purpose="Format address objects as strings"]
1134
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert address object to comma-separated string"]
1135
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1136
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted address string" errors="none" side_effects="none"]
1137
+ // CIL[DOC-MICRO logic_summary="Joins address components with commas, filters empty values" notable_decisions="[]"]
1138
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1139
+ // CIL[DOC-STABILITY stability="stable"]
1140
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1141
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1142
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1143
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1144
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1145
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
935
1146
  function formatAddress(data: any): string {
936
1147
  if (typeof data !== 'object' || data === null) {
937
1148
  return String(data)
@@ -947,8 +1158,21 @@ function formatAddress(data: any): string {
947
1158
 
948
1159
  return parts.join(', ')
949
1160
  }
950
-
951
- /** Joins a string array with ', '. Returns String(data) for non-arrays. */
1161
+ // CIL[END chunk_id="schemautils_formataddress_1784020784"]
1162
+ // CIL[BEGIN chunk_id="schemautils_formatmultiselect_1784020784" type="function" identity="formatMultiselect" parent="none"]
1163
+ // CIL[DOC-MACRO purpose="Format multiselect arrays as strings"]
1164
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert array to comma-separated string"]
1165
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1166
+ // CIL[DOC-CONTRACT inputs="none" outputs="comma-separated string" errors="none" side_effects="none"]
1167
+ // CIL[DOC-MICRO logic_summary="Joins array elements with commas, handles non-arrays" notable_decisions="[]"]
1168
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1169
+ // CIL[DOC-STABILITY stability="stable"]
1170
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1171
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1172
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1173
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1174
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1175
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
952
1176
  function formatMultiselect(data: string[]): string {
953
1177
  if (!Array.isArray(data)) {
954
1178
  return String(data)
@@ -956,8 +1180,21 @@ function formatMultiselect(data: string[]): string {
956
1180
 
957
1181
  return data.join(', ')
958
1182
  }
959
-
960
- /** Returns 'Active'/'Inactive' when context.field is 'is_active'; otherwise 'Yes'/'No'. */
1183
+ // CIL[END chunk_id="schemautils_formatmultiselect_1784020784"]
1184
+ // CIL[BEGIN chunk_id="schemautils_formatboolean_1784020784" type="function" identity="formatBoolean" parent="none"]
1185
+ // CIL[DOC-MACRO purpose="Format booleans with contextual labels"]
1186
+ // CIL[DOC-RESPONSIBILITY responsibility="Convert boolean to human-readable labels"]
1187
+ // CIL[DOC-CONTEXT module="schema-utils" layer="shared" related_files="[]"]
1188
+ // CIL[DOC-CONTRACT inputs="none" outputs="formatted boolean string" errors="none" side_effects="none"]
1189
+ // CIL[DOC-MICRO logic_summary="Uses contextual labels for is_active, defaults to Yes/No" notable_decisions="[]"]
1190
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1191
+ // CIL[DOC-STABILITY stability="stable"]
1192
+ // CIL[DOC-VECTOR type="context" content="Layer: shared. Direct callers: 0. Direct calls: 0. Related files: none."]
1193
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
1194
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
1195
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
1196
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1197
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
961
1198
  function formatBoolean(data: boolean, context?: any): string {
962
1199
  if (context?.field === 'is_active') {
963
1200
  return data ? 'Active' : 'Inactive'
@@ -965,3 +1202,4 @@ function formatBoolean(data: boolean, context?: any): string {
965
1202
 
966
1203
  return data ? 'Yes' : 'No'
967
1204
  }
1205
+ // CIL[END chunk_id="schemautils_formatboolean_1784020784"]