create-principles-disciple 1.146.4 → 1.146.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/package.json +3 -3
  3. package/release-manager/package.json +3 -3
  4. package/console/dist/ui/App.d.ts +0 -1
  5. package/console/dist/ui/App.js +0 -172
  6. package/console/dist/ui/api.d.ts +0 -611
  7. package/console/dist/ui/api.js +0 -1094
  8. package/console/dist/ui/components/auth/login-form.d.ts +0 -4
  9. package/console/dist/ui/components/auth/login-form.js +0 -142
  10. package/console/dist/ui/components/auth/splash-screen.d.ts +0 -9
  11. package/console/dist/ui/components/auth/splash-screen.js +0 -22
  12. package/console/dist/ui/components/error-boundary.d.ts +0 -15
  13. package/console/dist/ui/components/error-boundary.js +0 -30
  14. package/console/dist/ui/components/focus/daily-thought-card.d.ts +0 -1
  15. package/console/dist/ui/components/focus/daily-thought-card.js +0 -41
  16. package/console/dist/ui/components/language-switcher.d.ts +0 -2
  17. package/console/dist/ui/components/language-switcher.js +0 -17
  18. package/console/dist/ui/components/layout/app-sidebar.d.ts +0 -27
  19. package/console/dist/ui/components/layout/app-sidebar.js +0 -128
  20. package/console/dist/ui/components/layout/page-loading.d.ts +0 -13
  21. package/console/dist/ui/components/layout/page-loading.js +0 -14
  22. package/console/dist/ui/components/layout/page-shell.d.ts +0 -12
  23. package/console/dist/ui/components/layout/page-shell.js +0 -11
  24. package/console/dist/ui/components/layout/section-title.d.ts +0 -10
  25. package/console/dist/ui/components/layout/section-title.js +0 -9
  26. package/console/dist/ui/components/notifications/NotificationProvider.d.ts +0 -14
  27. package/console/dist/ui/components/notifications/NotificationProvider.js +0 -136
  28. package/console/dist/ui/components/notifications/favicon-badge.d.ts +0 -3
  29. package/console/dist/ui/components/notifications/favicon-badge.js +0 -68
  30. package/console/dist/ui/components/notifications/notification-reducer.d.ts +0 -9
  31. package/console/dist/ui/components/notifications/notification-reducer.js +0 -9
  32. package/console/dist/ui/components/notifications/sound-storage.d.ts +0 -3
  33. package/console/dist/ui/components/notifications/sound-storage.js +0 -37
  34. package/console/dist/ui/components/notifications/useNotifications.d.ts +0 -1
  35. package/console/dist/ui/components/notifications/useNotifications.js +0 -9
  36. package/console/dist/ui/components/onboarding/CircuitDiagram.d.ts +0 -8
  37. package/console/dist/ui/components/onboarding/CircuitDiagram.js +0 -18
  38. package/console/dist/ui/components/onboarding/DemoResultView.d.ts +0 -22
  39. package/console/dist/ui/components/onboarding/DemoResultView.js +0 -33
  40. package/console/dist/ui/components/onboarding/SlashCommandsCard.d.ts +0 -16
  41. package/console/dist/ui/components/onboarding/SlashCommandsCard.js +0 -20
  42. package/console/dist/ui/components/onboarding/slashCommands.d.ts +0 -20
  43. package/console/dist/ui/components/onboarding/slashCommands.js +0 -22
  44. package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.d.ts +0 -6
  45. package/console/dist/ui/components/receipts/ReceiptCoverageDisclosure.js +0 -66
  46. package/console/dist/ui/components/theme-provider.d.ts +0 -14
  47. package/console/dist/ui/components/theme-provider.js +0 -40
  48. package/console/dist/ui/components/theme-toggle.d.ts +0 -2
  49. package/console/dist/ui/components/theme-toggle.js +0 -11
  50. package/console/dist/ui/components/ui/alert-dialog.d.ts +0 -20
  51. package/console/dist/ui/components/ui/alert-dialog.js +0 -26
  52. package/console/dist/ui/components/ui/badge.d.ts +0 -9
  53. package/console/dist/ui/components/ui/badge.js +0 -22
  54. package/console/dist/ui/components/ui/button.d.ts +0 -11
  55. package/console/dist/ui/components/ui/button.js +0 -34
  56. package/console/dist/ui/components/ui/card.d.ts +0 -8
  57. package/console/dist/ui/components/ui/card.js +0 -16
  58. package/console/dist/ui/components/ui/charts.d.ts +0 -51
  59. package/console/dist/ui/components/ui/charts.js +0 -72
  60. package/console/dist/ui/components/ui/dialog.d.ts +0 -19
  61. package/console/dist/ui/components/ui/dialog.js +0 -22
  62. package/console/dist/ui/components/ui/input.d.ts +0 -3
  63. package/console/dist/ui/components/ui/input.js +0 -8
  64. package/console/dist/ui/components/ui/markdown.d.ts +0 -24
  65. package/console/dist/ui/components/ui/markdown.js +0 -119
  66. package/console/dist/ui/components/ui/progress-bar.d.ts +0 -20
  67. package/console/dist/ui/components/ui/progress-bar.js +0 -38
  68. package/console/dist/ui/components/ui/select.d.ts +0 -13
  69. package/console/dist/ui/components/ui/select.js +0 -26
  70. package/console/dist/ui/components/ui/separator.d.ts +0 -3
  71. package/console/dist/ui/components/ui/separator.js +0 -6
  72. package/console/dist/ui/components/ui/sheet.d.ts +0 -25
  73. package/console/dist/ui/components/ui/sheet.js +0 -37
  74. package/console/dist/ui/components/ui/shiny-text.d.ts +0 -28
  75. package/console/dist/ui/components/ui/shiny-text.js +0 -46
  76. package/console/dist/ui/components/ui/skeleton.d.ts +0 -3
  77. package/console/dist/ui/components/ui/skeleton.js +0 -6
  78. package/console/dist/ui/components/ui/sonner.d.ts +0 -3
  79. package/console/dist/ui/components/ui/sonner.js +0 -13
  80. package/console/dist/ui/components/ui/tooltip.d.ts +0 -7
  81. package/console/dist/ui/components/ui/tooltip.js +0 -10
  82. package/console/dist/ui/data/daily-thoughts.d.ts +0 -15
  83. package/console/dist/ui/data/daily-thoughts.js +0 -164
  84. package/console/dist/ui/hooks/useNotificationSound.d.ts +0 -12
  85. package/console/dist/ui/hooks/useNotificationSound.js +0 -74
  86. package/console/dist/ui/i18n/en.json +0 -2399
  87. package/console/dist/ui/i18n/index.d.ts +0 -2
  88. package/console/dist/ui/i18n/index.js +0 -32
  89. package/console/dist/ui/i18n/zh-CN.json +0 -2399
  90. package/console/dist/ui/main.d.ts +0 -1
  91. package/console/dist/ui/main.js +0 -9
  92. package/console/dist/ui/pages/ReportProblemValidators.d.ts +0 -144
  93. package/console/dist/ui/pages/ReportProblemValidators.js +0 -418
  94. package/console/dist/ui/pages/activation/ActivationPage.d.ts +0 -1
  95. package/console/dist/ui/pages/activation/ActivationPage.js +0 -512
  96. package/console/dist/ui/pages/activation/ActivationValidators.d.ts +0 -20
  97. package/console/dist/ui/pages/activation/ActivationValidators.js +0 -160
  98. package/console/dist/ui/pages/control-center/AgentCard.d.ts +0 -31
  99. package/console/dist/ui/pages/control-center/AgentCard.js +0 -212
  100. package/console/dist/ui/pages/control-center/AgentGroup.d.ts +0 -26
  101. package/console/dist/ui/pages/control-center/AgentGroup.js +0 -38
  102. package/console/dist/ui/pages/control-center/ControlCenterPage.d.ts +0 -1
  103. package/console/dist/ui/pages/control-center/ControlCenterPage.js +0 -509
  104. package/console/dist/ui/pages/control-center/DesignGatePanel.d.ts +0 -1
  105. package/console/dist/ui/pages/control-center/DesignGatePanel.js +0 -137
  106. package/console/dist/ui/pages/control-center/RuntimeProfileManager.d.ts +0 -31
  107. package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +0 -199
  108. package/console/dist/ui/pages/control-center/WorkflowDiagram.d.ts +0 -17
  109. package/console/dist/ui/pages/control-center/WorkflowDiagram.js +0 -55
  110. package/console/dist/ui/pages/debt/DebtPage.d.ts +0 -2
  111. package/console/dist/ui/pages/debt/DebtPage.js +0 -222
  112. package/console/dist/ui/pages/design-system/DesignSystemPage.d.ts +0 -2
  113. package/console/dist/ui/pages/design-system/DesignSystemPage.js +0 -30
  114. package/console/dist/ui/pages/examiner/ExaminerPage.d.ts +0 -15
  115. package/console/dist/ui/pages/examiner/ExaminerPage.js +0 -158
  116. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.d.ts +0 -1
  117. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +0 -421
  118. package/console/dist/ui/pages/focus/ApprovalSubjectReading.d.ts +0 -24
  119. package/console/dist/ui/pages/focus/ApprovalSubjectReading.js +0 -133
  120. package/console/dist/ui/pages/focus/FocusPage.d.ts +0 -123
  121. package/console/dist/ui/pages/focus/FocusPage.js +0 -567
  122. package/console/dist/ui/pages/focus/OwnerDecisionCard.d.ts +0 -9
  123. package/console/dist/ui/pages/focus/OwnerDecisionCard.js +0 -111
  124. package/console/dist/ui/pages/focus/OwnerWorkbench.d.ts +0 -19
  125. package/console/dist/ui/pages/focus/OwnerWorkbench.js +0 -75
  126. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +0 -34
  127. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +0 -298
  128. package/console/dist/ui/pages/focus/focus-validation.d.ts +0 -40
  129. package/console/dist/ui/pages/focus/focus-validation.js +0 -211
  130. package/console/dist/ui/pages/intent/IntentOnboarding.d.ts +0 -67
  131. package/console/dist/ui/pages/intent/IntentOnboarding.js +0 -289
  132. package/console/dist/ui/pages/intent/IntentPage.d.ts +0 -1
  133. package/console/dist/ui/pages/intent/IntentPage.js +0 -252
  134. package/console/dist/ui/pages/pain/PainEvidenceValidators.d.ts +0 -59
  135. package/console/dist/ui/pages/pain/PainEvidenceValidators.js +0 -125
  136. package/console/dist/ui/pages/pain/PainPage.d.ts +0 -1
  137. package/console/dist/ui/pages/pain/PainPage.js +0 -420
  138. package/console/dist/ui/pages/pain/pain-card-helpers.d.ts +0 -175
  139. package/console/dist/ui/pages/pain/pain-card-helpers.js +0 -181
  140. package/console/dist/ui/pages/principles/CorePrinciplesPage.d.ts +0 -10
  141. package/console/dist/ui/pages/principles/CorePrinciplesPage.js +0 -59
  142. package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +0 -60
  143. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +0 -602
  144. package/console/dist/ui/pages/principles/PrinciplesPage.d.ts +0 -1
  145. package/console/dist/ui/pages/principles/PrinciplesPage.js +0 -327
  146. package/console/dist/ui/pages/principles/PrinciplesViewNav.d.ts +0 -8
  147. package/console/dist/ui/pages/principles/PrinciplesViewNav.js +0 -15
  148. package/console/dist/ui/pages/report-problem/ReportProblemPage.d.ts +0 -1
  149. package/console/dist/ui/pages/report-problem/ReportProblemPage.js +0 -467
  150. package/console/dist/ui/pages/settings/SettingsPage.d.ts +0 -4
  151. package/console/dist/ui/pages/settings/SettingsPage.js +0 -457
  152. package/console/dist/ui/pages/settings/UpdatePage.d.ts +0 -1
  153. package/console/dist/ui/pages/settings/UpdatePage.js +0 -215
  154. package/console/dist/ui/pages/welcome/WelcomePage.d.ts +0 -5
  155. package/console/dist/ui/pages/welcome/WelcomePage.js +0 -187
  156. package/console/dist/ui/utils/agent-metadata.d.ts +0 -71
  157. package/console/dist/ui/utils/agent-metadata.js +0 -284
  158. package/console/dist/ui/utils/approval-review.d.ts +0 -30
  159. package/console/dist/ui/utils/approval-review.js +0 -50
  160. package/console/dist/ui/utils/approval-warning-localization.d.ts +0 -34
  161. package/console/dist/ui/utils/approval-warning-localization.js +0 -148
  162. package/console/dist/ui/utils/attention-presentation.d.ts +0 -35
  163. package/console/dist/ui/utils/attention-presentation.js +0 -46
  164. package/console/dist/ui/utils/blocker-localization.d.ts +0 -23
  165. package/console/dist/ui/utils/blocker-localization.js +0 -36
  166. package/console/dist/ui/utils/control-center-helpers.d.ts +0 -79
  167. package/console/dist/ui/utils/control-center-helpers.js +0 -189
  168. package/console/dist/ui/utils/enum-labels.d.ts +0 -50
  169. package/console/dist/ui/utils/enum-labels.js +0 -171
  170. package/console/dist/ui/utils/format-date.d.ts +0 -11
  171. package/console/dist/ui/utils/format-date.js +0 -17
  172. package/console/dist/ui/utils/onboarding-state.d.ts +0 -25
  173. package/console/dist/ui/utils/onboarding-state.js +0 -77
  174. package/console/dist/ui/utils/reading-layout.d.ts +0 -20
  175. package/console/dist/ui/utils/reading-layout.js +0 -46
  176. package/console/dist/ui/utils/rule-binding-validators.d.ts +0 -13
  177. package/console/dist/ui/utils/rule-binding-validators.js +0 -188
  178. package/console/dist/ui/utils/runtime-mode.d.ts +0 -11
  179. package/console/dist/ui/utils/runtime-mode.js +0 -11
  180. package/console/dist/ui/utils/token-storage.d.ts +0 -22
  181. package/console/dist/ui/utils/token-storage.js +0 -47
  182. package/console/dist/ui/utils/validators.d.ts +0 -1178
  183. package/console/dist/ui/utils/validators.js +0 -3297
  184. package/release-manager/dist/bootstrap-entry.d.ts +0 -2
  185. package/release-manager/dist/bootstrap-entry.d.ts.map +0 -1
  186. package/release-manager/dist/bootstrap-entry.js.map +0 -1
  187. package/release-manager/dist/i18n.d.ts +0 -6
  188. package/release-manager/dist/i18n.d.ts.map +0 -1
  189. package/release-manager/dist/i18n.js.map +0 -1
  190. package/release-manager/dist/index.d.ts +0 -7
  191. package/release-manager/dist/index.d.ts.map +0 -1
  192. package/release-manager/dist/index.js +0 -656
  193. package/release-manager/dist/index.js.map +0 -1
  194. package/release-manager/dist/installer.d.ts +0 -629
  195. package/release-manager/dist/installer.d.ts.map +0 -1
  196. package/release-manager/dist/installer.js.map +0 -1
  197. package/release-manager/dist/installers/codex-host-installer.d.ts +0 -39
  198. package/release-manager/dist/installers/codex-host-installer.d.ts.map +0 -1
  199. package/release-manager/dist/installers/codex-host-installer.js.map +0 -1
  200. package/release-manager/dist/installers/index.d.ts +0 -51
  201. package/release-manager/dist/installers/index.d.ts.map +0 -1
  202. package/release-manager/dist/installers/index.js.map +0 -1
  203. package/release-manager/dist/installers/openclaw-host-installer.d.ts +0 -14
  204. package/release-manager/dist/installers/openclaw-host-installer.d.ts.map +0 -1
  205. package/release-manager/dist/installers/openclaw-host-installer.js.map +0 -1
  206. package/release-manager/dist/installers/workbuddy-host-installer.d.ts +0 -335
  207. package/release-manager/dist/installers/workbuddy-host-installer.d.ts.map +0 -1
  208. package/release-manager/dist/installers/workbuddy-host-installer.js.map +0 -1
  209. package/release-manager/dist/mvp-config.d.ts +0 -233
  210. package/release-manager/dist/mvp-config.d.ts.map +0 -1
  211. package/release-manager/dist/mvp-config.js.map +0 -1
  212. package/release-manager/dist/prompts.d.ts +0 -43
  213. package/release-manager/dist/prompts.d.ts.map +0 -1
  214. package/release-manager/dist/prompts.js +0 -268
  215. package/release-manager/dist/prompts.js.map +0 -1
  216. package/release-manager/dist/skill-language.d.ts +0 -32
  217. package/release-manager/dist/skill-language.d.ts.map +0 -1
  218. package/release-manager/dist/skill-language.js.map +0 -1
  219. package/release-manager/dist/uninstaller.d.ts +0 -68
  220. package/release-manager/dist/uninstaller.d.ts.map +0 -1
  221. package/release-manager/dist/uninstaller.js +0 -746
  222. package/release-manager/dist/uninstaller.js.map +0 -1
  223. package/release-manager/dist/update/apply-payload.d.ts +0 -143
  224. package/release-manager/dist/update/apply-payload.d.ts.map +0 -1
  225. package/release-manager/dist/update/apply-payload.js.map +0 -1
  226. package/release-manager/dist/update/atomic-file.d.ts +0 -35
  227. package/release-manager/dist/update/atomic-file.d.ts.map +0 -1
  228. package/release-manager/dist/update/atomic-file.js.map +0 -1
  229. package/release-manager/dist/update/bootstrap-executor.d.ts +0 -21
  230. package/release-manager/dist/update/bootstrap-executor.d.ts.map +0 -1
  231. package/release-manager/dist/update/bootstrap-executor.js.map +0 -1
  232. package/release-manager/dist/update/bootstrap-protocol.d.ts +0 -56
  233. package/release-manager/dist/update/bootstrap-protocol.d.ts.map +0 -1
  234. package/release-manager/dist/update/bootstrap-protocol.js.map +0 -1
  235. package/release-manager/dist/update/channel-metadata.d.ts +0 -28
  236. package/release-manager/dist/update/channel-metadata.d.ts.map +0 -1
  237. package/release-manager/dist/update/channel-metadata.js.map +0 -1
  238. package/release-manager/dist/update/channel-promotion.d.ts +0 -39
  239. package/release-manager/dist/update/channel-promotion.d.ts.map +0 -1
  240. package/release-manager/dist/update/channel-promotion.js +0 -55
  241. package/release-manager/dist/update/channel-promotion.js.map +0 -1
  242. package/release-manager/dist/update/console-surface.d.ts +0 -19
  243. package/release-manager/dist/update/console-surface.d.ts.map +0 -1
  244. package/release-manager/dist/update/console-surface.js.map +0 -1
  245. package/release-manager/dist/update/data-compatibility.d.ts +0 -35
  246. package/release-manager/dist/update/data-compatibility.d.ts.map +0 -1
  247. package/release-manager/dist/update/data-compatibility.js +0 -45
  248. package/release-manager/dist/update/data-compatibility.js.map +0 -1
  249. package/release-manager/dist/update/install-layout.d.ts +0 -105
  250. package/release-manager/dist/update/install-layout.d.ts.map +0 -1
  251. package/release-manager/dist/update/install-layout.js.map +0 -1
  252. package/release-manager/dist/update/product-identity.d.ts +0 -70
  253. package/release-manager/dist/update/product-identity.d.ts.map +0 -1
  254. package/release-manager/dist/update/product-identity.js.map +0 -1
  255. package/release-manager/dist/update/release-asset-manifest.d.ts +0 -31
  256. package/release-manager/dist/update/release-asset-manifest.d.ts.map +0 -1
  257. package/release-manager/dist/update/release-asset-manifest.js.map +0 -1
  258. package/release-manager/dist/update/release-identity.d.ts +0 -33
  259. package/release-manager/dist/update/release-identity.d.ts.map +0 -1
  260. package/release-manager/dist/update/release-identity.js.map +0 -1
  261. package/release-manager/dist/update/release-manager-authority.d.ts +0 -39
  262. package/release-manager/dist/update/release-manager-authority.d.ts.map +0 -1
  263. package/release-manager/dist/update/release-manager-authority.js.map +0 -1
  264. package/release-manager/dist/update/release-manager.d.ts +0 -156
  265. package/release-manager/dist/update/release-manager.d.ts.map +0 -1
  266. package/release-manager/dist/update/release-manager.js.map +0 -1
  267. package/release-manager/dist/update/release-metadata-publisher.d.ts +0 -199
  268. package/release-manager/dist/update/release-metadata-publisher.d.ts.map +0 -1
  269. package/release-manager/dist/update/release-metadata-publisher.js +0 -478
  270. package/release-manager/dist/update/release-metadata-publisher.js.map +0 -1
  271. package/release-manager/dist/update/release-metadata-source.d.ts +0 -85
  272. package/release-manager/dist/update/release-metadata-source.d.ts.map +0 -1
  273. package/release-manager/dist/update/release-metadata-source.js.map +0 -1
  274. package/release-manager/dist/update/release-metadata.d.ts +0 -60
  275. package/release-manager/dist/update/release-metadata.d.ts.map +0 -1
  276. package/release-manager/dist/update/release-metadata.js.map +0 -1
  277. package/release-manager/dist/update/release-policy.d.ts +0 -51
  278. package/release-manager/dist/update/release-policy.d.ts.map +0 -1
  279. package/release-manager/dist/update/release-policy.js.map +0 -1
  280. package/release-manager/dist/update/rollback-policy.d.ts +0 -56
  281. package/release-manager/dist/update/rollback-policy.d.ts.map +0 -1
  282. package/release-manager/dist/update/rollback-policy.js +0 -61
  283. package/release-manager/dist/update/rollback-policy.js.map +0 -1
  284. package/release-manager/dist/update/transaction-journal.d.ts +0 -212
  285. package/release-manager/dist/update/transaction-journal.d.ts.map +0 -1
  286. package/release-manager/dist/update/transaction-journal.js.map +0 -1
  287. package/release-manager/dist/update/trust-metadata.d.ts +0 -55
  288. package/release-manager/dist/update/trust-metadata.d.ts.map +0 -1
  289. package/release-manager/dist/update/trust-metadata.js.map +0 -1
  290. package/release-manager/dist/update/trust-root-provisioning.d.ts +0 -99
  291. package/release-manager/dist/update/trust-root-provisioning.d.ts.map +0 -1
  292. package/release-manager/dist/update/trust-root-provisioning.js.map +0 -1
  293. package/release-manager/dist/update/update-history.d.ts +0 -46
  294. package/release-manager/dist/update/update-history.d.ts.map +0 -1
  295. package/release-manager/dist/update/update-history.js +0 -123
  296. package/release-manager/dist/update/update-history.js.map +0 -1
  297. package/release-manager/dist/utils/config-file-io.d.ts +0 -34
  298. package/release-manager/dist/utils/config-file-io.d.ts.map +0 -1
  299. package/release-manager/dist/utils/config-file-io.js.map +0 -1
  300. package/release-manager/dist/utils/env.d.ts +0 -94
  301. package/release-manager/dist/utils/env.d.ts.map +0 -1
  302. package/release-manager/dist/utils/env.js.map +0 -1
  303. package/release-manager/dist/utils/logger.d.ts +0 -15
  304. package/release-manager/dist/utils/logger.d.ts.map +0 -1
  305. package/release-manager/dist/utils/logger.js.map +0 -1
  306. package/release-manager/dist/utils/workspace-divergence.d.ts +0 -36
  307. package/release-manager/dist/utils/workspace-divergence.d.ts.map +0 -1
  308. package/release-manager/dist/utils/workspace-divergence.js.map +0 -1
@@ -1,3297 +0,0 @@
1
- // PRI-613: feedback submit-ladder data shapes derive from the canonical shared
2
- // schema contract via `import type` ONLY — a runtime import would bundle
3
- // @sinclair/typebox into the browser (~850KB, measured; SPEC §10.5 stop
4
- // condition). The runtime const FEEDBACK_CHANNEL_IDS lives in a separate
5
- // dependency-free module so browser code can check channel IDs safely.
6
- import { FEEDBACK_CHANNEL_IDS } from '../../shared/feedback-channel-ids.js';
7
- // ── Primitive guards ──────────────────────────────────────────────────────────
8
- function isString(v) {
9
- return typeof v === 'string';
10
- }
11
- function isNumber(v) {
12
- return typeof v === 'number' && !Number.isNaN(v);
13
- }
14
- function isBoolean(v) {
15
- return typeof v === 'boolean';
16
- }
17
- function isObject(v) {
18
- return typeof v === 'object' && v !== null && !Array.isArray(v);
19
- }
20
- /** Read a nullable string field from an untrusted object. */
21
- function readNullableString(v, key) {
22
- if (!Object.hasOwn(v, key))
23
- return { valid: true, value: null };
24
- const val = v[key];
25
- if (val === null || typeof val === 'string')
26
- return { valid: true, value: val };
27
- return { valid: false };
28
- }
29
- /** Read a nullable number field from an untrusted object. */
30
- function readNullableNumber(v, key) {
31
- if (!Object.hasOwn(v, key))
32
- return { valid: true, value: null };
33
- const val = v[key];
34
- if (val === null || (typeof val === 'number' && !Number.isNaN(val)))
35
- return { valid: true, value: val };
36
- return { valid: false };
37
- }
38
- // ── Generic array element validator ───────────────────────────────────────────
39
- function validateArray(v, validateElement) {
40
- if (!Array.isArray(v))
41
- return null;
42
- const result = [];
43
- for (const el of v) {
44
- const validated = validateElement(el);
45
- if (validated === null)
46
- return null;
47
- result.push(validated);
48
- }
49
- return result;
50
- }
51
- const governanceSourceTypes = new Set(['principle', 'artifact', 'task', 'run', 'approval', 'activation', 'trajectory']);
52
- const governanceChannels = new Set(['prompt', 'code_tool_hook', 'defer_archive']);
53
- const governanceHeadlineCodes = new Set(['governance.headline.owner_decision', 'governance.headline.recovery', 'governance.headline.revision', 'governance.headline.processing', 'governance.headline.active', 'governance.headline.unavailable', 'governance.headline.recorded']);
54
- const governanceReasonCodes = new Set(['governance.reason.approval_pending', 'governance.reason.recovery_required', 'governance.reason.automatic_revision', 'governance.reason.processing', 'governance.reason.activation_active', 'governance.reason.data_incomplete', 'governance.reason.no_current_process']);
55
- const governanceNextActionCodes = new Set(['governance.next.review', 'governance.next.inspect_recovery', 'governance.next.wait', 'governance.next.monitor', 'governance.next.inspect_data', 'governance.next.none']);
56
- const lineageConfidences = new Set(['strong', 'weak', 'unknown']);
57
- const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
58
- function isGovernanceTimestamp(value) {
59
- if (typeof value !== 'string' || !timestampPattern.test(value))
60
- return false;
61
- const parsed = new Date(value);
62
- if (!Number.isFinite(parsed.getTime()))
63
- return false;
64
- return parsed.toISOString() === (value.includes('.') ? value : value.replace('Z', '.000Z'));
65
- }
66
- function hasOwnFields(value, fields) {
67
- return fields.every(field => Object.hasOwn(value, field));
68
- }
69
- const PROMPT_BUDGET_FALLBACK_REASONS = new Set([
70
- 'host_capacity_absent',
71
- 'host_capacity_source_untrusted',
72
- 'host_capacity_invalid_value',
73
- 'catalog_unavailable',
74
- 'model_identity_absent',
75
- 'catalog_no_exact_match',
76
- 'catalog_no_match_for_model_id',
77
- 'catalog_ambiguous_model_id',
78
- 'catalog_provider_absent',
79
- 'catalog_metadata_invalid',
80
- // PRI-1017: console-side bounded code — a runtime observation exists but
81
- // is too old to be presented as the current budget.
82
- 'runtime_budget_observation_stale',
83
- ]);
84
- function isPromptBudgetFallbackReason(value) {
85
- return isString(value) && PROMPT_BUDGET_FALLBACK_REASONS.has(value);
86
- }
87
- function isPromptBudgetCapacitySource(value) {
88
- return value === 'operator_declared' || value === 'host_effective_window' || value === 'pi_catalog_exact' || value === 'legacy_fallback';
89
- }
90
- function isPromptBudgetCapacityStatus(value) {
91
- return value === 'confirmed' || value === 'unknown';
92
- }
93
- function isStringEnum(value, allowed) {
94
- return typeof value === 'string' && allowed.has(value);
95
- }
96
- function isGovernanceSourceRef(value) {
97
- return isObject(value) && hasOwnFields(value, ['type', 'id'])
98
- && isStringEnum(value.type, governanceSourceTypes) && typeof value.id === 'string' && value.id.length > 0;
99
- }
100
- function isGovernanceSourceRefs(value) {
101
- return Array.isArray(value) && value.every(isGovernanceSourceRef);
102
- }
103
- function isDataQualityIssue(value) {
104
- return isObject(value) && hasOwnFields(value, ['source', 'reasonCode', 'nextActionCode'])
105
- && isStringEnum(value.source, new Set(['ledger', 'artifact', 'task', 'approval', 'activation', 'trajectory', 'lineage']))
106
- && typeof value.reasonCode === 'string' && value.reasonCode.length > 0
107
- && typeof value.nextActionCode === 'string' && value.nextActionCode.length > 0
108
- && (!Object.hasOwn(value, 'sourceRef') || isGovernanceSourceRef(value.sourceRef));
109
- }
110
- function isTimelineEvent(value) {
111
- return isObject(value) && hasOwnFields(value, ['code', 'recordedAt', 'summaryCode', 'sourceRef', 'lineageConfidence'])
112
- && isStringEnum(value.code, new Set(['pain_created', 'candidate_generated', 'review_started', 'revision_requested', 'revision_reopened', 'approved', 'rejected', 'activated', 'deactivated', 'failed', 'human_review']))
113
- && isGovernanceTimestamp(value.recordedAt)
114
- && (!Object.hasOwn(value, 'occurredAt') || isGovernanceTimestamp(value.occurredAt))
115
- && typeof value.summaryCode === 'string' && value.summaryCode.length > 0
116
- && isGovernanceSourceRef(value.sourceRef) && isStringEnum(value.lineageConfidence, lineageConfidences);
117
- }
118
- function isOwnerGovernanceView(value) {
119
- if (!isObject(value) || !hasOwnFields(value, ['schemaVersion', 'principleId', 'asOf', 'summary', 'principleState', 'process', 'automation', 'attention', 'activationSummary', 'timeline', 'sourceRefs', 'dataQuality']))
120
- return false;
121
- if (value.schemaVersion !== '1' || typeof value.principleId !== 'string' || value.principleId.length === 0 || !isGovernanceTimestamp(value.asOf))
122
- return false;
123
- const { summary, principleState: principle, process, automation, attention, activationSummary: activation, dataQuality: quality, } = value;
124
- if (!isObject(summary) || !hasOwnFields(summary, ['headlineCode', 'reasonCode', 'nextActionCode', 'ownerActionRequired', 'sourceRefs'])
125
- || !isStringEnum(summary.headlineCode, governanceHeadlineCodes) || !isStringEnum(summary.reasonCode, governanceReasonCodes) || !isStringEnum(summary.nextActionCode, governanceNextActionCodes)
126
- || typeof summary.ownerActionRequired !== 'boolean' || !isGovernanceSourceRefs(summary.sourceRefs)
127
- || (Object.hasOwn(summary, 'safeReasonSummary') && (typeof summary.safeReasonSummary !== 'string' || summary.safeReasonSummary.length === 0)))
128
- return false;
129
- if (!isObject(principle) || !hasOwnFields(principle, ['value', 'sourceRefs'])
130
- || !isStringEnum(principle.value, new Set(['candidate', 'active', 'archived', 'deprecated', 'probation'])) || !isGovernanceSourceRefs(principle.sourceRefs))
131
- return false;
132
- if (!isObject(process) || !hasOwnFields(process, ['sourceRefs']) || !isGovernanceSourceRefs(process.sourceRefs)
133
- || (Object.hasOwn(process, 'stage') && !isStringEnum(process.stage, new Set(['generating', 'reviewing', 'revising', 'approval', 'activation'])))
134
- || (Object.hasOwn(process, 'currentTaskKind') && !isStringEnum(process.currentTaskKind, new Set(['dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator', 'rollout_reviewer']))))
135
- return false;
136
- if (!isObject(automation) || !hasOwnFields(automation, ['state', 'sourceRefs'])
137
- || !isStringEnum(automation.state, new Set(['idle', 'queued', 'running', 'retry_scheduled', 'stalled'])) || !isGovernanceSourceRefs(automation.sourceRefs))
138
- return false;
139
- if (!isObject(attention) || !hasOwnFields(attention, ['primary', 'items'])
140
- || !isStringEnum(attention.primary, new Set(['none', 'owner_required', 'recovery_required'])) || !Array.isArray(attention.items)
141
- || !attention.items.every(item => isObject(item) && hasOwnFields(item, ['kind', 'reasonCode', 'sourceRef'])
142
- && isStringEnum(item.kind, new Set(['owner_decision', 'recovery'])) && typeof item.reasonCode === 'string' && item.reasonCode.length > 0 && isGovernanceSourceRef(item.sourceRef)))
143
- return false;
144
- if (!isObject(activation) || !hasOwnFields(activation, ['state', 'channels', 'observedChannels', 'sourceRefs'])
145
- || !isStringEnum(activation.state, new Set(['none', 'active', 'partially_active', 'deactivated']))
146
- || !Array.isArray(activation.channels) || !activation.channels.every(channel => isStringEnum(channel, governanceChannels))
147
- || !Array.isArray(activation.observedChannels) || !activation.observedChannels.every(channel => isStringEnum(channel, governanceChannels))
148
- || !isGovernanceSourceRefs(activation.sourceRefs))
149
- return false;
150
- return Array.isArray(value.timeline) && value.timeline.every(isTimelineEvent) && isGovernanceSourceRefs(value.sourceRefs)
151
- && isObject(quality) && hasOwnFields(quality, ['degraded', 'issues']) && typeof quality.degraded === 'boolean'
152
- && Array.isArray(quality.issues) && quality.issues.every(isDataQualityIssue);
153
- }
154
- export function validateOwnerGovernanceView(value) {
155
- return isOwnerGovernanceView(value) ? value : null;
156
- }
157
- // ── Owner Decision Experience v1 — browser-local structural validator ───────
158
- //
159
- // Mirrors the authoritative TypeBox contract in principles-core
160
- // (owner-decision-view-contract.ts). Type-only import keeps the browser bundle
161
- // free of the Node-oriented core barrel (same reason as the governance
162
- // validator above, ERR-100).
163
- const ownerDecisionStates = new Set(['needs_owner_decision', 'processing', 'blocked', 'recovery_needed', 'decided', 'no_action']);
164
- const ownerInboxGroups = new Set(['decision', 'blocked', 'recovery', 'none']);
165
- const ownerInboxAttention = new Set(['individual', 'aggregate', 'none']);
166
- function isOwnerInboxItem(value) {
167
- // Codex review P2 fix: the page dereferences item.inbox.attention,
168
- // item.principleId and item.nextAction.ownerText on every entry — a
169
- // malformed entry (e.g. `[null]` from a version-skewed server) must be
170
- // rejected here instead of crashing the render.
171
- if (!isObject(value) || !hasOwnFields(value, ['principleId', 'decisionState', 'learnedPrinciple', 'inbox', 'blockerCodes', 'nextAction']))
172
- return false;
173
- if (!isString(value.principleId) || value.principleId.length === 0)
174
- return false;
175
- if (!isString(value.decisionState) || !ownerDecisionStates.has(value.decisionState))
176
- return false;
177
- const principle = value.learnedPrinciple;
178
- if (!isObject(principle))
179
- return false;
180
- if (principle.status === 'known') {
181
- if (!hasOwnFields(principle, ['text', 'sourceTier'])
182
- || !isString(principle.text) || principle.text.length === 0
183
- || !isString(principle.sourceTier))
184
- return false;
185
- }
186
- else if (principle.status === 'unknown') {
187
- if (!hasOwnFields(principle, ['reasonText']) || !isString(principle.reasonText))
188
- return false;
189
- }
190
- else
191
- return false;
192
- if (!isObject(value.inbox) || !hasOwnFields(value.inbox, ['group', 'attention'])
193
- || !isStringEnum(value.inbox.group, ownerInboxGroups)
194
- || !isStringEnum(value.inbox.attention, ownerInboxAttention))
195
- return false;
196
- if (!Array.isArray(value.blockerCodes) || !value.blockerCodes.every(isString))
197
- return false;
198
- return isObject(value.nextAction) && hasOwnFields(value.nextAction, ['ownerText'])
199
- && isString(value.nextAction.ownerText);
200
- }
201
- function isOwnerReason(value) {
202
- if (!isObject(value) || !hasOwnFields(value, ['code', 'ownerText', 'sourceRefs']))
203
- return false;
204
- if (!isString(value.code) || value.code.length === 0)
205
- return false;
206
- if (!isString(value.ownerText) || value.ownerText.length === 0)
207
- return false;
208
- if (!Array.isArray(value.sourceRefs))
209
- return false;
210
- // PRI-875 (CodeRabbit P2): `count` is optional, but when present it must be
211
- // an integer ≥ 1 — a malformed value from a version-skewed server would
212
- // otherwise reach the UI and be interpolated into the `{{n}}` template.
213
- if (!hasOwnFields(value, ['count']))
214
- return true;
215
- return typeof value.count === 'number' && Number.isInteger(value.count) && value.count >= 1;
216
- }
217
- function isOwnerAction(value) {
218
- return isObject(value) && hasOwnFields(value, ['key', 'semantic', 'label', 'channel', 'confirmation', 'expectedConsequence'])
219
- && isString(value.key) && value.key.length > 0
220
- && isString(value.semantic) && value.semantic.length > 0
221
- && isString(value.label)
222
- && isObject(value.confirmation) && typeof value.confirmation.required === 'boolean' && isString(value.confirmation.text);
223
- }
224
- export function isOwnerDecisionViewCore(value) {
225
- if (!isObject(value) || !hasOwnFields(value, ['schemaVersion', 'principleId', 'asOf', 'sourceReadStatus', 'sourceReads',
226
- 'incidentSummary', 'learnedPrinciple', 'rationale', 'applicability', 'nonApplicabilityOrUnknown', 'expectedBehavior',
227
- 'currentEnforcement', 'evidenceSummary', 'uncertainty', 'risk', 'rollback', 'decisionState', 'decisionSubjects',
228
- 'availableActions', 'blockers', 'nextAction', 'inbox', 'technicalDetails']))
229
- return false;
230
- if (value.schemaVersion !== '1' || !isString(value.principleId) || !isString(value.asOf))
231
- return false;
232
- if (value.sourceReadStatus !== 'complete' && value.sourceReadStatus !== 'partial')
233
- return false;
234
- if (!isStringEnum(value.decisionState, ownerDecisionStates))
235
- return false;
236
- if (!Array.isArray(value.availableActions) || !value.availableActions.every(isOwnerAction))
237
- return false;
238
- if (!Array.isArray(value.blockers) || !value.blockers.every(item => isObject(item) && hasOwnFields(item, ['subjectKey', 'kind', 'reason']) && isOwnerReason(item.reason)))
239
- return false;
240
- return isObject(value.nextAction) && hasOwnFields(value.nextAction, ['code', 'ownerText'])
241
- && isString(value.nextAction.ownerText)
242
- && isObject(value.inbox) && hasOwnFields(value.inbox, ['group', 'attention', 'reason']) && isOwnerReason(value.inbox.reason);
243
- }
244
- export function validateOwnerDecisionViewCore(value) {
245
- return isOwnerDecisionViewCore(value) ? value : null;
246
- }
247
- export function isOwnerDecisionInbox(value) {
248
- return isObject(value)
249
- && hasOwnFields(value, ['schemaVersion', 'generatedAt', 'totalPrinciples', 'groups', 'historicalUnknown'])
250
- && value.schemaVersion === '1'
251
- && isString(value.generatedAt)
252
- && typeof value.totalPrinciples === 'number'
253
- && isObject(value.groups) && hasOwnFields(value.groups, ['decision', 'blocked', 'recovery'])
254
- && ['decision', 'blocked', 'recovery'].every((key) => {
255
- const group = value.groups[key];
256
- return Array.isArray(group) && group.every(isOwnerInboxItem);
257
- })
258
- && isObject(value.historicalUnknown) && typeof (value.historicalUnknown).count === 'number';
259
- }
260
- export function validateOwnerDecisionInbox(value) {
261
- return isOwnerDecisionInbox(value) ? value : null;
262
- }
263
- function isOwnerDecisionLibraryEntry(value) {
264
- return isObject(value) && hasOwnFields(value, ['decisionState', 'learnedPrinciple'])
265
- && isString(value.decisionState)
266
- && isObject(value.learnedPrinciple)
267
- && (value.learnedPrinciple.status === 'known'
268
- ? isString(value.learnedPrinciple.text) && isString(value.learnedPrinciple.sourceTier)
269
- : value.learnedPrinciple.status === 'unknown' && isString(value.learnedPrinciple.reasonText));
270
- }
271
- export function isOwnerDecisionLibrary(value) {
272
- return isObject(value)
273
- && hasOwnFields(value, ['schemaVersion', 'generatedAt', 'entries'])
274
- && value.schemaVersion === '1'
275
- && isString(value.generatedAt)
276
- && isObject(value.entries)
277
- && Object.values(value.entries).every(isOwnerDecisionLibraryEntry);
278
- }
279
- export function validateOwnerDecisionLibrary(value) {
280
- return isOwnerDecisionLibrary(value) ? value : null;
281
- }
282
- // ── PRI-586: Governance Experience Snapshot v1.5.1 (browser-local validator) ──
283
- // Mirrors the authoritative TypeBox contract in principles-core
284
- // (governance-experience-contract.ts). Type-only import above keeps the client
285
- // bundle free of the Node-oriented core barrel (ERR-100).
286
- const governancePrimaryAttentions = new Set(['setup_required', 'owner_decision_required', 'recovery_required', 'degraded', 'background_processing', 'all_clear']);
287
- const governanceExperienceReasonCodes = new Set([
288
- 'governance.exp.reason.owner_identity_missing', 'governance.exp.reason.owner_identity_invalid',
289
- 'governance.exp.reason.owner_authentication_missing', 'governance.exp.reason.approval_pending',
290
- 'governance.exp.reason.rulecode_owner_decision', 'governance.exp.reason.no_pending_decision',
291
- 'governance.exp.reason.owner_decision_available', 'governance.exp.reason.break_glass_entry',
292
- 'governance.exp.reason.recovery_required', 'governance.exp.reason.source_unavailable',
293
- 'governance.exp.reason.config_invalid', 'governance.exp.reason.processing',
294
- 'governance.exp.reason.workspace_clear', 'governance.exp.reason.workspace_empty',
295
- ]);
296
- const governanceExperienceNextActionCodes = new Set([
297
- 'governance.exp.next.configure_owner', 'governance.exp.next.authenticate_console', 'governance.exp.next.review_approvals',
298
- 'governance.exp.next.inspect_recovery', 'governance.exp.next.inspect_sources',
299
- 'governance.exp.next.fix_config', 'governance.exp.next.monitor', 'governance.exp.next.none',
300
- ]);
301
- const governanceObservedAuthorities = new Set(['operator_legacy', 'configured_owner', 'break_glass']);
302
- const governanceActionKinds = new Set(['principle_approval', 'rulecode_owner_decision', 'emergency_pause']);
303
- const governanceActivityCategories = new Set(['blocked', 'needs_recovery', 'needs_decision', 'processing']);
304
- const governanceEnvironmentValues = new Set(['production', 'development', 'demo', 'test', 'unknown']);
305
- const governanceEnvironmentSources = new Set(['workspace_config', 'missing']);
306
- const governanceIssueGroupSources = new Set(['ledger', 'artifact', 'task', 'approval', 'activation', 'trajectory', 'lineage', 'workspace']);
307
- function isGovernanceActivityItem(value) {
308
- return isObject(value) && hasOwnFields(value, ['category', 'reasonCode', 'sourceRefs'])
309
- && isStringEnum(value.category, governanceActivityCategories)
310
- && typeof value.reasonCode === 'string' && value.reasonCode.length > 0
311
- && isGovernanceSourceRefs(value.sourceRefs)
312
- && (!Object.hasOwn(value, 'principleId') || (typeof value.principleId === 'string' && value.principleId.length > 0));
313
- }
314
- function isGovernanceActionReadiness(value) {
315
- return isObject(value) && hasOwnFields(value, ['kind', 'observedAuthority', 'status', 'reasonCode', 'nextActionCode'])
316
- && isStringEnum(value.kind, governanceActionKinds)
317
- && isStringEnum(value.observedAuthority, governanceObservedAuthorities)
318
- && isStringEnum(value.status, new Set(['entry_conditions_met', 'blocked']))
319
- && isStringEnum(value.reasonCode, governanceExperienceReasonCodes)
320
- && isStringEnum(value.nextActionCode, governanceExperienceNextActionCodes);
321
- }
322
- function isEnvironmentContext(value) {
323
- return isObject(value) && hasOwnFields(value, ['environment', 'source'])
324
- && isStringEnum(value.environment, governanceEnvironmentValues)
325
- && isStringEnum(value.source, governanceEnvironmentSources)
326
- && (!Object.hasOwn(value, 'configIssue') || (typeof value.configIssue === 'string' && value.configIssue.length > 0));
327
- }
328
- function isGovernanceExperienceSnapshot(value) {
329
- if (!isObject(value) || !hasOwnFields(value, ['schemaVersion', 'snapshotId', 'asOf', 'summary', 'readiness', 'activity', 'trustContext', 'dataQuality']))
330
- return false;
331
- if (value.schemaVersion !== '1' || typeof value.snapshotId !== 'string' || value.snapshotId.length === 0 || !isGovernanceTimestamp(value.asOf))
332
- return false;
333
- const { summary, readiness, activity, trustContext, dataQuality } = value;
334
- if (!isObject(summary) || !hasOwnFields(summary, ['primaryAttention', 'headlineCode', 'reasonCode', 'nextActionCode'])
335
- || !isStringEnum(summary.primaryAttention, governancePrimaryAttentions)
336
- || typeof summary.headlineCode !== 'string' || summary.headlineCode.length === 0
337
- || !isStringEnum(summary.reasonCode, governanceExperienceReasonCodes)
338
- || !isStringEnum(summary.nextActionCode, governanceExperienceNextActionCodes))
339
- return false;
340
- if (!isObject(readiness) || !hasOwnFields(readiness, ['authenticationMode', 'ownerIdentityConfiguration', 'governanceActions'])
341
- || !isStringEnum(readiness.authenticationMode, new Set(['authenticated', 'no_auth']))
342
- || !isStringEnum(readiness.ownerIdentityConfiguration, new Set(['configured', 'missing', 'invalid']))
343
- || !Array.isArray(readiness.governanceActions) || readiness.governanceActions.length !== 3
344
- || !readiness.governanceActions.every(isGovernanceActionReadiness))
345
- return false;
346
- if (!isObject(activity) || !hasOwnFields(activity, ['primaryAttention', 'categories'])
347
- || !isStringEnum(activity.primaryAttention, governancePrimaryAttentions)
348
- || !Array.isArray(activity.categories)
349
- || !activity.categories.every((category) => isObject(category)
350
- && hasOwnFields(category, ['category', 'count', 'items', 'hasMore'])
351
- && isStringEnum(category.category, governanceActivityCategories)
352
- && typeof category.count === 'number' && Number.isInteger(category.count) && category.count >= 0
353
- && Array.isArray(category.items) && category.items.length <= 10 && category.items.every(isGovernanceActivityItem)
354
- && typeof category.hasMore === 'boolean'))
355
- return false;
356
- if (!isObject(trustContext) || !hasOwnFields(trustContext, ['environmentContext', 'lineageTransparency'])
357
- || !isEnvironmentContext(trustContext.environmentContext)
358
- || !isObject(trustContext.lineageTransparency)
359
- || !hasOwnFields(trustContext.lineageTransparency, ['confidence', 'strongViewCount', 'weakViewCount', 'unknownViewCount'])
360
- || !isStringEnum(trustContext.lineageTransparency.confidence, lineageConfidences)
361
- || [trustContext.lineageTransparency.strongViewCount, trustContext.lineageTransparency.weakViewCount, trustContext.lineageTransparency.unknownViewCount]
362
- .some(count => typeof count !== 'number' || !Number.isInteger(count) || count < 0))
363
- return false;
364
- return isObject(dataQuality) && hasOwnFields(dataQuality, ['degraded', 'issueGroups', 'hasMore'])
365
- && typeof dataQuality.degraded === 'boolean' && typeof dataQuality.hasMore === 'boolean'
366
- && Array.isArray(dataQuality.issueGroups) && dataQuality.issueGroups.length <= 10
367
- && dataQuality.issueGroups.every((group) => isObject(group)
368
- && hasOwnFields(group, ['source', 'reasonCode', 'nextActionCode', 'count', 'sampleRefs'])
369
- && isStringEnum(group.source, governanceIssueGroupSources)
370
- && typeof group.reasonCode === 'string' && group.reasonCode.length > 0
371
- && typeof group.nextActionCode === 'string' && group.nextActionCode.length > 0
372
- && typeof group.count === 'number' && Number.isInteger(group.count) && group.count >= 1
373
- && isGovernanceSourceRefs(group.sampleRefs));
374
- }
375
- export function validateGovernanceExperienceSnapshot(value) {
376
- return isGovernanceExperienceSnapshot(value) ? value : null;
377
- }
378
- export function validateErrorResponse(v) {
379
- if (!isObject(v))
380
- return null;
381
- const result = {};
382
- if (Object.hasOwn(v, 'message') && isString(v.message)) {
383
- result.message = v.message;
384
- }
385
- if (Object.hasOwn(v, 'error') && isString(v.error)) {
386
- result.error = v.error;
387
- }
388
- // N4 (PR-1083 review): surfacing the machine-readable `reason` field lets
389
- // the UI branch on structured error codes instead of parsing natural
390
- // language out of nextAction. Previously `nextAction.includes("32KB")`
391
- // would silently regress to "saveFailed" the moment the backend phrased
392
- // the cap differently or returned localized text.
393
- if (Object.hasOwn(v, 'reason') && isString(v.reason)) {
394
- result.reason = v.reason;
395
- }
396
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction)) {
397
- result.nextAction = v.nextAction;
398
- }
399
- return result;
400
- }
401
- // ── Request headers validator ─────────────────────────────────────────────────
402
- /**
403
- * Validates that `options.headers` is a plain object with string values.
404
- * Returns null if the value is not a valid headers object.
405
- * Used instead of `as Record<string, string>` on untrusted RequestInit.headers.
406
- */
407
- export function validateHeaders(v) {
408
- if (v === undefined || v === null)
409
- return null;
410
- if (!isObject(v))
411
- return null;
412
- const result = {};
413
- for (const key of Object.keys(v)) {
414
- const value = v[key];
415
- if (!isString(value))
416
- return null;
417
- result[key] = value;
418
- }
419
- return result;
420
- }
421
- export function validateFeedbackReport(v) {
422
- if (!isObject(v))
423
- return null;
424
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
425
- return null;
426
- if (!Object.hasOwn(v, 'createdAt') || !isString(v.createdAt))
427
- return null;
428
- if (!Object.hasOwn(v, 'report') || !isObject(v.report))
429
- return null;
430
- return { id: v.id, createdAt: v.createdAt, report: v.report };
431
- }
432
- export function validateFeedbackDraftsList(v) {
433
- if (!isObject(v))
434
- return null;
435
- if (!Object.hasOwn(v, 'drafts') || !Array.isArray(v.drafts))
436
- return null;
437
- return validateArray(v.drafts, (item) => {
438
- if (!isObject(item))
439
- return null;
440
- if (!Object.hasOwn(item, 'id') || !isString(item.id))
441
- return null;
442
- if (!Object.hasOwn(item, 'createdAt') || !isString(item.createdAt))
443
- return null;
444
- if (!Object.hasOwn(item, 'type') || !isString(item.type))
445
- return null;
446
- if (!Object.hasOwn(item, 'title') || !isString(item.title))
447
- return null;
448
- return { id: item.id, createdAt: item.createdAt, type: item.type, title: item.title };
449
- });
450
- }
451
- export function validateFeedbackDraftEnvelope(v) {
452
- if (!isObject(v))
453
- return null;
454
- if (!Object.hasOwn(v, 'report') || !isObject(v.report))
455
- return null;
456
- return { report: v.report };
457
- }
458
- export function validateDeleteEnvelope(v) {
459
- if (!isObject(v))
460
- return null;
461
- if (!Object.hasOwn(v, 'deleted') || !isBoolean(v.deleted))
462
- return null;
463
- return { deleted: v.deleted };
464
- }
465
- function isValidChannelStatus(item) {
466
- if (!Object.hasOwn(item, 'id') || !isString(item.id) || !FEEDBACK_CHANNEL_IDS.includes(item.id))
467
- return null;
468
- if (!Object.hasOwn(item, 'available') || !isBoolean(item.available))
469
- return null;
470
- const reason = readNullableString(item, 'reason');
471
- const nextAction = readNullableString(item, 'nextAction');
472
- if (!reason.valid || !nextAction.valid)
473
- return null;
474
- const status = { id: item.id, available: item.available };
475
- if (reason.value !== null)
476
- status.reason = reason.value;
477
- if (nextAction.value !== null)
478
- status.nextAction = nextAction.value;
479
- return status;
480
- }
481
- export function validateFeedbackChannels(v) {
482
- if (!isObject(v))
483
- return null;
484
- if (!Object.hasOwn(v, 'channels') || !Array.isArray(v.channels))
485
- return null;
486
- // Per-item leniency preserved: invalid channel entries are skipped, the
487
- // envelope is not rejected (legacy behavior — a bad single probe must not
488
- // hide the other channels).
489
- const channels = [];
490
- for (const item of v.channels) {
491
- if (!isObject(item))
492
- continue;
493
- const status = isValidChannelStatus(item);
494
- if (status !== null)
495
- channels.push(status);
496
- }
497
- return { channels };
498
- }
499
- export function validateFeedbackSubmitResult(v) {
500
- if (!isObject(v))
501
- return null;
502
- if (!Object.hasOwn(v, 'ok') || !isBoolean(v.ok))
503
- return null;
504
- if (!Object.hasOwn(v, 'alreadySubmitted') || !isBoolean(v.alreadySubmitted))
505
- return null;
506
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
507
- return null;
508
- const result = {
509
- ok: v.ok,
510
- alreadySubmitted: v.alreadySubmitted,
511
- status: v.status,
512
- };
513
- const submittedVia = readNullableString(v, 'submittedVia');
514
- const trackingId = readNullableString(v, 'trackingId');
515
- const externalUrl = readNullableString(v, 'externalUrl');
516
- const nextAction = readNullableString(v, 'nextAction');
517
- if (!submittedVia.valid || !trackingId.valid || !externalUrl.valid || !nextAction.valid)
518
- return null;
519
- if (submittedVia.value !== null)
520
- result.submittedVia = submittedVia.value;
521
- if (trackingId.value !== null)
522
- result.trackingId = trackingId.value;
523
- if (externalUrl.value !== null)
524
- result.externalUrl = externalUrl.value;
525
- if (nextAction.value !== null)
526
- result.nextAction = nextAction.value;
527
- if (Object.hasOwn(v, 'writeBackFailed') && !isBoolean(v.writeBackFailed))
528
- return null;
529
- if (isBoolean(v.writeBackFailed))
530
- result.writeBackFailed = v.writeBackFailed;
531
- return result;
532
- }
533
- function validateWorkspaceConfig(v) {
534
- if (!isObject(v))
535
- return null;
536
- if (!Object.hasOwn(v, 'workspaceName') || !isString(v.workspaceName))
537
- return null;
538
- if (!Object.hasOwn(v, 'enabled') || !isBoolean(v.enabled))
539
- return null;
540
- const displayName = readNullableString(v, 'displayName');
541
- if (!displayName.valid)
542
- return null;
543
- if (!Object.hasOwn(v, 'syncEnabled') || !isBoolean(v.syncEnabled))
544
- return null;
545
- return {
546
- workspaceName: v.workspaceName,
547
- enabled: v.enabled,
548
- displayName: displayName.value,
549
- syncEnabled: v.syncEnabled,
550
- };
551
- }
552
- export function validateWorkspaceEntry(v) {
553
- if (!isObject(v))
554
- return null;
555
- if (!Object.hasOwn(v, 'name') || !isString(v.name))
556
- return null;
557
- if (!Object.hasOwn(v, 'path') || !isString(v.path))
558
- return null;
559
- const lastSync = readNullableString(v, 'lastSync');
560
- if (!lastSync.valid)
561
- return null;
562
- let config = null;
563
- if (Object.hasOwn(v, 'config') && v.config !== null) {
564
- config = validateWorkspaceConfig(v.config);
565
- if (config === null)
566
- return null;
567
- }
568
- return {
569
- name: v.name,
570
- path: v.path,
571
- lastSync: lastSync.value,
572
- config,
573
- };
574
- }
575
- export function validateWorkspaceList(v) {
576
- return validateArray(v, validateWorkspaceEntry);
577
- }
578
- export function validateRemovedEnvelope(v) {
579
- if (!isObject(v))
580
- return null;
581
- if (!Object.hasOwn(v, 'removed') || !isString(v.removed))
582
- return null;
583
- return { removed: v.removed };
584
- }
585
- export function validateSyncResult(v) {
586
- if (!isObject(v))
587
- return null;
588
- if (!Object.hasOwn(v, 'success') || !isBoolean(v.success))
589
- return null;
590
- if (!Object.hasOwn(v, 'syncedAt') || !isString(v.syncedAt))
591
- return null;
592
- return { success: v.success, syncedAt: v.syncedAt };
593
- }
594
- function validateReadinessStatus(v) {
595
- switch (v) {
596
- case 'ready':
597
- case 'not_ready':
598
- case 'needs_setup':
599
- case 'disabled':
600
- case 'unknown':
601
- return v;
602
- default:
603
- return null;
604
- }
605
- }
606
- function validateRuntimeProfileSummary(v) {
607
- if (!isObject(v))
608
- return null;
609
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
610
- return null;
611
- if (!Object.hasOwn(v, 'type') || !isString(v.type))
612
- return null;
613
- if (!Object.hasOwn(v, 'label') || !isString(v.label))
614
- return null;
615
- if (Object.hasOwn(v, 'apiKeyEnv') && !isString(v.apiKeyEnv))
616
- return null;
617
- if (Object.hasOwn(v, 'provider') && !isString(v.provider))
618
- return null;
619
- if (Object.hasOwn(v, 'model') && !isString(v.model))
620
- return null;
621
- if (!Object.hasOwn(v, 'readiness'))
622
- return null;
623
- const readiness = validateReadinessStatus(v.readiness);
624
- if (readiness === null)
625
- return null;
626
- return {
627
- id: v.id, type: v.type, label: v.label,
628
- ...(Object.hasOwn(v, 'apiKeyEnv') && isString(v.apiKeyEnv) ? { apiKeyEnv: v.apiKeyEnv } : {}),
629
- ...(Object.hasOwn(v, 'provider') && isString(v.provider) ? { provider: v.provider } : {}),
630
- ...(Object.hasOwn(v, 'model') && isString(v.model) ? { model: v.model } : {}),
631
- readiness,
632
- };
633
- }
634
- function validateFeatureSummary(v) {
635
- if (!isObject(v))
636
- return null;
637
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
638
- return null;
639
- if (!Object.hasOwn(v, 'category') || !isString(v.category))
640
- return null;
641
- if (!Object.hasOwn(v, 'enabled') || !isBoolean(v.enabled))
642
- return null;
643
- return { id: v.id, category: v.category, enabled: v.enabled };
644
- }
645
- function validateAgentSummary(v) {
646
- if (!isObject(v))
647
- return null;
648
- if (!Object.hasOwn(v, 'name') || !isString(v.name))
649
- return null;
650
- if (!Object.hasOwn(v, 'enabled') || !isBoolean(v.enabled))
651
- return null;
652
- if (!Object.hasOwn(v, 'runtimeProfileId') || !isString(v.runtimeProfileId))
653
- return null;
654
- if (!Object.hasOwn(v, 'runtimeProfileLabel') || !isString(v.runtimeProfileLabel))
655
- return null;
656
- if (!Object.hasOwn(v, 'readiness'))
657
- return null;
658
- const readiness = validateReadinessStatus(v.readiness);
659
- if (readiness === null)
660
- return null;
661
- return { name: v.name, enabled: v.enabled, runtimeProfileId: v.runtimeProfileId, runtimeProfileLabel: v.runtimeProfileLabel, readiness };
662
- }
663
- function validateConfigError(v) {
664
- if (!isObject(v))
665
- return null;
666
- if (!Object.hasOwn(v, 'path') || !isString(v.path))
667
- return null;
668
- if (!Object.hasOwn(v, 'reason') || !isString(v.reason))
669
- return null;
670
- if (!Object.hasOwn(v, 'nextAction') || !isString(v.nextAction))
671
- return null;
672
- return { path: v.path, reason: v.reason, nextAction: v.nextAction };
673
- }
674
- function validateConfigSource(v) {
675
- switch (v) {
676
- case 'defaults':
677
- case 'user_config':
678
- return v;
679
- default:
680
- return null;
681
- }
682
- }
683
- export function validateConfigSummary(v) {
684
- if (!isObject(v))
685
- return null;
686
- if (!Object.hasOwn(v, 'version') || !isNumber(v.version))
687
- return null;
688
- if (!Object.hasOwn(v, 'source'))
689
- return null;
690
- const source = validateConfigSource(v.source);
691
- if (source === null)
692
- return null;
693
- if (!Object.hasOwn(v, 'features') || !Array.isArray(v.features))
694
- return null;
695
- if (!Object.hasOwn(v, 'runtimeProfiles') || !Array.isArray(v.runtimeProfiles))
696
- return null;
697
- if (!Object.hasOwn(v, 'defaultRuntime') || !isString(v.defaultRuntime))
698
- return null;
699
- if (!Object.hasOwn(v, 'agents') || !Array.isArray(v.agents))
700
- return null;
701
- if (!Object.hasOwn(v, 'ui') || !isObject(v.ui))
702
- return null;
703
- if (!Object.hasOwn(v, 'warnings') || !Array.isArray(v.warnings))
704
- return null;
705
- const features = validateArray(v.features, validateFeatureSummary);
706
- if (features === null)
707
- return null;
708
- const runtimeProfiles = validateArray(v.runtimeProfiles, validateRuntimeProfileSummary);
709
- if (runtimeProfiles === null)
710
- return null;
711
- const agents = validateArray(v.agents, validateAgentSummary);
712
- if (agents === null)
713
- return null;
714
- const warnings = validateArray(v.warnings, (el) => isString(el) ? el : null);
715
- if (warnings === null)
716
- return null;
717
- // ui.diagnostics.mode
718
- const { ui } = v;
719
- if (!Object.hasOwn(ui, 'diagnostics') || !isObject(ui.diagnostics))
720
- return null;
721
- if (!Object.hasOwn(ui.diagnostics, 'mode') || !isString(ui.diagnostics.mode))
722
- return null;
723
- let errors;
724
- if (Object.hasOwn(v, 'errors') && Array.isArray(v.errors)) {
725
- errors = validateArray(v.errors, validateConfigError) ?? undefined;
726
- }
727
- return {
728
- version: v.version, source, features, runtimeProfiles,
729
- defaultRuntime: v.defaultRuntime, agents,
730
- ui: { diagnostics: { mode: ui.diagnostics.mode } },
731
- warnings, errors,
732
- };
733
- }
734
- export function validateConfigCatalog(v) {
735
- if (!isObject(v))
736
- return null;
737
- if (!Object.hasOwn(v, 'profiles') || !Array.isArray(v.profiles))
738
- return null;
739
- const profiles = validateArray(v.profiles, validateRuntimeProfileSummary);
740
- if (profiles === null)
741
- return null;
742
- let errors;
743
- if (Object.hasOwn(v, 'errors') && Array.isArray(v.errors)) {
744
- errors = validateArray(v.errors, validateConfigError) ?? undefined;
745
- }
746
- return { profiles, errors };
747
- }
748
- export function validateAgentBindingUpdate(v) {
749
- if (!isObject(v))
750
- return null;
751
- if (!Object.hasOwn(v, 'agent') || !isString(v.agent))
752
- return null;
753
- if (!Object.hasOwn(v, 'runtimeProfile') || !isString(v.runtimeProfile))
754
- return null;
755
- if (!Object.hasOwn(v, 'enabled') || !isBoolean(v.enabled))
756
- return null;
757
- return { agent: v.agent, runtimeProfile: v.runtimeProfile, enabled: v.enabled };
758
- }
759
- export function validateFeatureFlagUpdate(v) {
760
- if (!isObject(v))
761
- return null;
762
- if (!Object.hasOwn(v, 'feature') || !isString(v.feature))
763
- return null;
764
- if (!Object.hasOwn(v, 'enabled') || !isBoolean(v.enabled))
765
- return null;
766
- return { feature: v.feature, enabled: v.enabled };
767
- }
768
- export function validateReadinessCheck(v) {
769
- if (!isObject(v))
770
- return null;
771
- if (!Object.hasOwn(v, 'agent') || !isString(v.agent))
772
- return null;
773
- if (!Object.hasOwn(v, 'readiness'))
774
- return null;
775
- const readiness = validateReadinessStatus(v.readiness);
776
- if (readiness === null)
777
- return null;
778
- if (!Object.hasOwn(v, 'profileId') || !isString(v.profileId))
779
- return null;
780
- if (!Object.hasOwn(v, 'profileLabel') || !isString(v.profileLabel))
781
- return null;
782
- const result = { agent: v.agent, readiness, profileId: v.profileId, profileLabel: v.profileLabel };
783
- if (Object.hasOwn(v, 'reason') && isString(v.reason))
784
- result.reason = v.reason;
785
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
786
- result.nextAction = v.nextAction;
787
- return result;
788
- }
789
- export function validateDefaultRuntimeUpdate(v) {
790
- if (!isObject(v))
791
- return null;
792
- if (!Object.hasOwn(v, 'defaultRuntime') || !isString(v.defaultRuntime))
793
- return null;
794
- return { defaultRuntime: v.defaultRuntime };
795
- }
796
- export function validateRuntimeProfileMutation(v) {
797
- if (!isObject(v))
798
- return null;
799
- if (!Object.hasOwn(v, 'profileId') || !isString(v.profileId))
800
- return null;
801
- if (!Object.hasOwn(v, 'profile') || !isObject(v.profile))
802
- return null;
803
- const { profile } = v;
804
- if (!Object.hasOwn(profile, 'type') || !isString(profile.type))
805
- return null;
806
- // Bind narrowed string to a local so the return literal keeps `type: string`
807
- // (spreading a Record<string, unknown> alone would widen `type` back to unknown).
808
- const profileType = profile.type;
809
- return { profileId: v.profileId, profile: { ...profile, type: profileType } };
810
- }
811
- function validateHealthCheckItem(v) {
812
- if (!isObject(v))
813
- return null;
814
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
815
- return null;
816
- if (!Object.hasOwn(v, 'name') || !isString(v.name))
817
- return null;
818
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
819
- return null;
820
- if (!Object.hasOwn(v, 'message') || !isString(v.message))
821
- return null;
822
- if (!Object.hasOwn(v, 'lastCheck') || !isString(v.lastCheck))
823
- return null;
824
- return { id: v.id, name: v.name, status: v.status, message: v.message, lastCheck: v.lastCheck };
825
- }
826
- export function validateConfigReadiness(v) {
827
- if (!isObject(v))
828
- return null;
829
- if (!Object.hasOwn(v, 'checks') || !Array.isArray(v.checks))
830
- return null;
831
- if (!Object.hasOwn(v, 'generatedAt') || !isString(v.generatedAt))
832
- return null;
833
- const checks = validateArray(v.checks, validateHealthCheckItem);
834
- if (checks === null)
835
- return null;
836
- return { checks, generatedAt: v.generatedAt };
837
- }
838
- function validateStagnationSignal(v) {
839
- if (!isObject(v))
840
- return null;
841
- if (!Object.hasOwn(v, 'type') || !isString(v.type))
842
- return null;
843
- if (!Object.hasOwn(v, 'principleId') || !isString(v.principleId))
844
- return null;
845
- if (!Object.hasOwn(v, 'daysSince') || !isNumber(v.daysSince))
846
- return null;
847
- return { type: v.type, principleId: v.principleId, daysSince: v.daysSince };
848
- }
849
- const VALID_GOVERNANCE_STATES = new Set(['none', 'in_progress', 'owner_review_ready', 'degraded']);
850
- const VALID_STATE_REASON_CODES = new Set([
851
- 'state_db_missing', 'no_pipeline_activity', 'pending_approvals',
852
- 'tasks_need_human_review',
853
- 'pipeline_active', 'consumed_candidates', 'degraded_state',
854
- ]);
855
- const VALID_NEXT_ACTION_CODES = new Set([
856
- 'run_config_doctor', 'wait_for_pipeline', 'review_approvals',
857
- 'review_failed_tasks',
858
- 'check_degraded_signals', 'check_pipeline_status',
859
- ]);
860
- const VALID_DEGRADED_REASON_CODES = new Set(['task_retry_wait', 'task_failed', 'approval_table_missing', 'trajectory_db_unavailable']);
861
- const VALID_DEGRADED_NEXT_ACTION_CODES = new Set(['check_task_status', 'fix_and_retry', 'run_integrity_check', 'check_trajectory_db']);
862
- function validateDegradedSignal(v) {
863
- if (!isObject(v))
864
- return null;
865
- if (!Object.hasOwn(v, 'reasonCode') || !isString(v.reasonCode))
866
- return null;
867
- if (!VALID_DEGRADED_REASON_CODES.has(v.reasonCode))
868
- return null;
869
- if (!Object.hasOwn(v, 'nextActionCode') || !isString(v.nextActionCode))
870
- return null;
871
- if (!VALID_DEGRADED_NEXT_ACTION_CODES.has(v.nextActionCode))
872
- return null;
873
- if (!Object.hasOwn(v, 'reason') || !isString(v.reason))
874
- return null;
875
- if (!Object.hasOwn(v, 'nextAction') || !isString(v.nextAction))
876
- return null;
877
- if (!Object.hasOwn(v, 'source') || !isString(v.source))
878
- return null;
879
- return {
880
- reasonCode: v.reasonCode,
881
- nextActionCode: v.nextActionCode,
882
- reason: v.reason,
883
- nextAction: v.nextAction,
884
- source: v.source,
885
- };
886
- }
887
- function validateFailedTaskDetailRun(v) {
888
- if (!isObject(v))
889
- return null;
890
- if (!Object.hasOwn(v, 'runId') || !isString(v.runId))
891
- return null;
892
- // Required by the RunRecord contract and by the lineage check in
893
- // validateFailedTaskDetail (rc-6): a run must name the task it belongs to.
894
- if (!Object.hasOwn(v, 'taskId') || !isString(v.taskId))
895
- return null;
896
- if (!Object.hasOwn(v, 'attemptNumber') || !isNumber(v.attemptNumber))
897
- return null;
898
- if (!Object.hasOwn(v, 'executionStatus') || !isString(v.executionStatus))
899
- return null;
900
- if (!Object.hasOwn(v, 'startedAt') || !isString(v.startedAt))
901
- return null;
902
- const endedAt = readNullableString(v, 'endedAt');
903
- if (!endedAt.valid)
904
- return null;
905
- const reason = readNullableString(v, 'reason');
906
- if (!reason.valid)
907
- return null;
908
- const errorCategory = readNullableString(v, 'errorCategory');
909
- if (!errorCategory.valid)
910
- return null;
911
- return {
912
- runId: v.runId,
913
- taskId: v.taskId,
914
- attemptNumber: v.attemptNumber,
915
- executionStatus: v.executionStatus,
916
- startedAt: v.startedAt,
917
- endedAt: endedAt.value,
918
- reason: reason.value,
919
- errorCategory: errorCategory.value,
920
- };
921
- }
922
- export function validateFailedTaskDetail(v) {
923
- if (!isObject(v))
924
- return null;
925
- if (!Object.hasOwn(v, 'task') || !isObject(v.task))
926
- return null;
927
- if (!Object.hasOwn(v, 'runs') || !Array.isArray(v.runs))
928
- return null;
929
- const t = v.task;
930
- if (!Object.hasOwn(t, 'taskId') || !isString(t.taskId))
931
- return null;
932
- if (!Object.hasOwn(t, 'taskKind') || !isString(t.taskKind))
933
- return null;
934
- if (!Object.hasOwn(t, 'status') || !isString(t.status))
935
- return null;
936
- if (!Object.hasOwn(t, 'createdAt') || !isString(t.createdAt))
937
- return null;
938
- // updatedAt is required on the server's TaskRecord (typebox: no .optional())
939
- // — required here too, so contract drift reaches the validation-error state
940
- // instead of a silently partial record (rc-3).
941
- if (!Object.hasOwn(t, 'updatedAt') || !isString(t.updatedAt))
942
- return null;
943
- if (!Object.hasOwn(t, 'attemptCount') || !isNumber(t.attemptCount))
944
- return null;
945
- if (!Object.hasOwn(t, 'maxAttempts') || !isNumber(t.maxAttempts))
946
- return null;
947
- const taskLastError = readNullableString(t, 'lastError');
948
- if (!taskLastError.valid)
949
- return null;
950
- const topLastError = readNullableString(v, 'lastError');
951
- if (!topLastError.valid)
952
- return null;
953
- const runs = validateArray(v.runs, validateFailedTaskDetailRun);
954
- if (runs === null)
955
- return null;
956
- // rc-6-lineage-consistency: every run must belong to THIS task — a detail
957
- // response mixing another task's runs would display foreign failure reasons.
958
- for (const run of runs) {
959
- if (run.taskId !== t.taskId)
960
- return null;
961
- }
962
- return {
963
- taskId: t.taskId,
964
- taskKind: t.taskKind,
965
- status: t.status,
966
- createdAt: t.createdAt,
967
- updatedAt: t.updatedAt,
968
- attemptCount: t.attemptCount,
969
- maxAttempts: t.maxAttempts,
970
- lastError: topLastError.value ?? taskLastError.value,
971
- runs,
972
- };
973
- }
974
- export function validateRecoveryResult(v) {
975
- if (!isObject(v))
976
- return null;
977
- if (!Object.hasOwn(v, 'taskId') || !isString(v.taskId))
978
- return null;
979
- if (!Object.hasOwn(v, 'previousStatus') || !isString(v.previousStatus))
980
- return null;
981
- if (!Object.hasOwn(v, 'newStatus') || !isString(v.newStatus))
982
- return null;
983
- if (!Object.hasOwn(v, 'result') || !isString(v.result))
984
- return null;
985
- const result = {
986
- taskId: v.taskId,
987
- previousStatus: v.previousStatus,
988
- newStatus: v.newStatus,
989
- result: v.result,
990
- };
991
- if (Object.hasOwn(v, 'forceApplied')) {
992
- if (!isBoolean(v.forceApplied))
993
- return null;
994
- result.forceApplied = v.forceApplied;
995
- }
996
- if (Object.hasOwn(v, 'nextAction')) {
997
- if (!isString(v.nextAction))
998
- return null;
999
- result.nextAction = v.nextAction;
1000
- }
1001
- return result;
1002
- }
1003
- export function validateGovernanceQueue(v) {
1004
- if (!isObject(v))
1005
- return null;
1006
- if (!Object.hasOwn(v, 'pendingReviewCount') || !isNumber(v.pendingReviewCount))
1007
- return null;
1008
- if (!Object.hasOwn(v, 'behaviorDeviationCount') || !isNumber(v.behaviorDeviationCount))
1009
- return null;
1010
- if (!Object.hasOwn(v, 'stagnationSignals') || !Array.isArray(v.stagnationSignals))
1011
- return null;
1012
- if (!Object.hasOwn(v, 'governanceState') || !isString(v.governanceState))
1013
- return null;
1014
- if (!VALID_GOVERNANCE_STATES.has(v.governanceState))
1015
- return null;
1016
- if (!Object.hasOwn(v, 'stateReasonCode') || !isString(v.stateReasonCode))
1017
- return null;
1018
- if (!VALID_STATE_REASON_CODES.has(v.stateReasonCode))
1019
- return null;
1020
- if (!Object.hasOwn(v, 'nextActionCode') || !isString(v.nextActionCode))
1021
- return null;
1022
- if (!VALID_NEXT_ACTION_CODES.has(v.nextActionCode))
1023
- return null;
1024
- if (!Object.hasOwn(v, 'stateReason') || !isString(v.stateReason))
1025
- return null;
1026
- if (!Object.hasOwn(v, 'nextAction') || !isString(v.nextAction))
1027
- return null;
1028
- const signals = validateArray(v.stagnationSignals, validateStagnationSignal);
1029
- if (signals === null)
1030
- return null;
1031
- const result = {
1032
- pendingReviewCount: v.pendingReviewCount,
1033
- behaviorDeviationCount: v.behaviorDeviationCount,
1034
- stagnationSignals: signals,
1035
- governanceState: VALID_GOVERNANCE_STATES.has(v.governanceState)
1036
- ? v.governanceState
1037
- : 'none',
1038
- stateReasonCode: v.stateReasonCode,
1039
- nextActionCode: v.nextActionCode,
1040
- stateReason: v.stateReason,
1041
- nextAction: v.nextAction,
1042
- };
1043
- // Optional fields — fail loud when present but wrong type (ERR-009)
1044
- if (Object.hasOwn(v, 'inProgressSummary')) {
1045
- if (!isString(v.inProgressSummary))
1046
- return null;
1047
- result.inProgressSummary = v.inProgressSummary;
1048
- }
1049
- if (Object.hasOwn(v, 'degradedSignals')) {
1050
- if (!Array.isArray(v.degradedSignals))
1051
- return null;
1052
- const ds = validateArray(v.degradedSignals, validateDegradedSignal);
1053
- if (ds === null)
1054
- return null;
1055
- result.degradedSignals = ds;
1056
- }
1057
- if (Object.hasOwn(v, 'note')) {
1058
- if (!isString(v.note))
1059
- return null;
1060
- result.note = v.note;
1061
- }
1062
- if (Object.hasOwn(v, 'generatedAt')) {
1063
- if (!isString(v.generatedAt))
1064
- return null;
1065
- result.generatedAt = v.generatedAt;
1066
- }
1067
- // PRI-380: evidence in progress count
1068
- if (Object.hasOwn(v, 'evidenceInProgressCount')) {
1069
- if (!isNumber(v.evidenceInProgressCount))
1070
- return null;
1071
- result.evidenceInProgressCount = v.evidenceInProgressCount;
1072
- }
1073
- // Governance Recovery Actions v1: needs_human_review task count
1074
- if (Object.hasOwn(v, 'pendingHumanReviewCount')) {
1075
- if (!isNumber(v.pendingHumanReviewCount))
1076
- return null;
1077
- result.pendingHumanReviewCount = v.pendingHumanReviewCount;
1078
- }
1079
- // Wave 4: gate blocks today (seconds-level auto-blocks)
1080
- if (Object.hasOwn(v, 'gateBlocksToday')) {
1081
- if (!isNumber(v.gateBlocksToday))
1082
- return null;
1083
- result.gateBlocksToday = v.gateBlocksToday;
1084
- }
1085
- return result;
1086
- }
1087
- const PROMPT_INJECTION_STATUS_KINDS = new Set([
1088
- 'injectable', 'oversized', 'model_dependent', 'window_excluded', 'resolution_failed', 'route_unconfirmed',
1089
- ]);
1090
- /** rc-1..rc-4: validate the per-activation injection status as untrusted input. */
1091
- function validatePromptActivationInjectionStatus(v) {
1092
- if (!isObject(v))
1093
- return null;
1094
- if (!Object.hasOwn(v, 'activationId') || !isString(v.activationId))
1095
- return null;
1096
- if (!Object.hasOwn(v, 'status') || !isString(v.status) || !PROMPT_INJECTION_STATUS_KINDS.has(v.status))
1097
- return null;
1098
- const result = { activationId: v.activationId, status: v.status };
1099
- if (Object.hasOwn(v, 'inCurrentWindow') && typeof v.inCurrentWindow === 'boolean')
1100
- result.inCurrentWindow = v.inCurrentWindow;
1101
- if (Object.hasOwn(v, 'costChars') && typeof v.costChars === 'number' && Number.isInteger(v.costChars) && v.costChars >= 0)
1102
- result.costChars = v.costChars;
1103
- if (Object.hasOwn(v, 'budget') && typeof v.budget === 'number' && Number.isInteger(v.budget) && v.budget > 0)
1104
- result.budget = v.budget;
1105
- if (Object.hasOwn(v, 'budgetScope') && (v.budgetScope === 'selected_lines' || v.budgetScope === 'full_directive_context'))
1106
- result.budgetScope = v.budgetScope;
1107
- if (Object.hasOwn(v, 'route') && (v.route === 'legacy_trim' || v.route === 'shared_render'))
1108
- result.route = v.route;
1109
- if (Object.hasOwn(v, 'reason') && isString(v.reason))
1110
- result.reason = v.reason;
1111
- if (Object.hasOwn(v, 'perHostFits') && Array.isArray(v.perHostFits)) {
1112
- const perHostFits = [];
1113
- for (const entry of v.perHostFits) {
1114
- if (!isObject(entry))
1115
- return null;
1116
- if ((entry.hostKind !== 'openclaw' && entry.hostKind !== 'codex' && entry.hostKind !== 'workbuddy')
1117
- || (entry.tierFit !== 'fits_all_tiers' && entry.tierFit !== 'model_dependent' && entry.tierFit !== 'exceeds_max_tier'))
1118
- return null;
1119
- perHostFits.push({ hostKind: entry.hostKind, tierFit: entry.tierFit });
1120
- }
1121
- result.perHostFits = perHostFits;
1122
- }
1123
- return result;
1124
- }
1125
- function validateActivationRecord(v) {
1126
- if (!isObject(v))
1127
- return null;
1128
- if (!Object.hasOwn(v, 'activationId') || !isString(v.activationId))
1129
- return null;
1130
- if (!Object.hasOwn(v, 'artifactId') || !isString(v.artifactId))
1131
- return null;
1132
- if (!Object.hasOwn(v, 'principleId') || !isString(v.principleId))
1133
- return null;
1134
- if (!Object.hasOwn(v, 'channel') || !isString(v.channel))
1135
- return null;
1136
- if (!Object.hasOwn(v, 'action') || !isString(v.action))
1137
- return null;
1138
- if (!Object.hasOwn(v, 'targetRef') || !isString(v.targetRef))
1139
- return null;
1140
- const activatedAt = readNullableString(v, 'activatedAt');
1141
- if (!activatedAt.valid)
1142
- return null;
1143
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1144
- return null;
1145
- const enforcement = Object.hasOwn(v, 'enforcement') && (v.enforcement === 'eligible' || v.enforcement === 'safety_isolated')
1146
- ? v.enforcement
1147
- : undefined;
1148
- return {
1149
- activationId: v.activationId, artifactId: v.artifactId, principleId: v.principleId,
1150
- channel: v.channel, action: v.action, targetRef: v.targetRef,
1151
- activatedAt: activatedAt.value,
1152
- status: v.status,
1153
- ...(enforcement ? { enforcement } : {}),
1154
- ...(Object.hasOwn(v, 'legacyDecisionUnknown') && v.legacyDecisionUnknown === true ? { legacyDecisionUnknown: true } : {}),
1155
- ...(Object.hasOwn(v, 'ownerReviewDueAt') && isString(v.ownerReviewDueAt) ? { ownerReviewDueAt: v.ownerReviewDueAt } : {}),
1156
- ...(Object.hasOwn(v, 'promptInjection')
1157
- ? (() => { const s = validatePromptActivationInjectionStatus(v.promptInjection); return s !== null ? { promptInjection: s } : {}; })()
1158
- : {}),
1159
- };
1160
- }
1161
- export function validateActivations(v) {
1162
- if (!isObject(v))
1163
- return null;
1164
- if (!Object.hasOwn(v, 'activations') || !Array.isArray(v.activations))
1165
- return null;
1166
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1167
- return null;
1168
- const activations = validateArray(v.activations, validateActivationRecord);
1169
- if (activations === null)
1170
- return null;
1171
- const result = { activations, status: v.status };
1172
- if (Object.hasOwn(v, 'reason') && isString(v.reason))
1173
- result.reason = v.reason;
1174
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
1175
- result.nextAction = v.nextAction;
1176
- if (Object.hasOwn(v, 'promptInjectionRoute') && isObject(v.promptInjectionRoute)) {
1177
- const route = v.promptInjectionRoute;
1178
- if ((route.status === 'confirmed' || route.status === 'unconfirmed') && typeof route.flagEnabled === 'boolean') {
1179
- const summary = { status: route.status, flagEnabled: route.flagEnabled };
1180
- if (Object.hasOwn(route, 'hostKind') && (route.hostKind === 'openclaw' || route.hostKind === 'codex' || route.hostKind === 'workbuddy'))
1181
- summary.hostKind = route.hostKind;
1182
- if (Object.hasOwn(route, 'route') && (route.route === 'legacy_trim' || route.route === 'shared_render'))
1183
- summary.route = route.route;
1184
- if (Object.hasOwn(route, 'unconfirmedReason') && isString(route.unconfirmedReason))
1185
- summary.unconfirmedReason = route.unconfirmedReason;
1186
- if (Object.hasOwn(route, 'nextAction') && isString(route.nextAction))
1187
- summary.nextAction = route.nextAction;
1188
- if (Object.hasOwn(route, 'globalReason') && isString(route.globalReason))
1189
- summary.globalReason = route.globalReason;
1190
- // PRI-1017: runtime budget observation timestamp the per-row budget
1191
- // figures were computed under (additive, drop-on-invalid).
1192
- if (Object.hasOwn(route, 'budgetObservedAt') && isString(route.budgetObservedAt) && route.budgetObservedAt.length > 0 && Number.isFinite(Date.parse(route.budgetObservedAt))) {
1193
- summary.budgetObservedAt = route.budgetObservedAt;
1194
- }
1195
- if (Object.hasOwn(route, 'budgetCapacitySource') && isPromptBudgetCapacitySource(route.budgetCapacitySource)) {
1196
- summary.budgetCapacitySource = route.budgetCapacitySource;
1197
- }
1198
- if (Object.hasOwn(route, 'budgetCapacityStatus') && isPromptBudgetCapacityStatus(route.budgetCapacityStatus)) {
1199
- summary.budgetCapacityStatus = route.budgetCapacityStatus;
1200
- }
1201
- if (Object.hasOwn(route, 'budgetContextWindowTokens') && typeof route.budgetContextWindowTokens === 'number'
1202
- && Number.isInteger(route.budgetContextWindowTokens) && route.budgetContextWindowTokens > 0) {
1203
- summary.budgetContextWindowTokens = route.budgetContextWindowTokens;
1204
- }
1205
- if (Object.hasOwn(route, 'budgetFallbackReason') && isPromptBudgetFallbackReason(route.budgetFallbackReason)) {
1206
- summary.budgetFallbackReason = route.budgetFallbackReason;
1207
- }
1208
- result.promptInjectionRoute = summary;
1209
- }
1210
- }
1211
- return result;
1212
- }
1213
- function validateTelemetryWindow(value) {
1214
- if (!isObject(value))
1215
- return null;
1216
- const keys = ['eligible', 'matched', 'blocked', 'unhealthy'];
1217
- if (!keys.every(key => value[key] === null || isNumber(value[key])) || !isNumber(value.circuitTrips))
1218
- return null;
1219
- if (value.toolDistribution !== null && (!isObject(value.toolDistribution) || !Object.values(value.toolDistribution).every(isNumber)))
1220
- return null;
1221
- return { eligible: typeof value.eligible === 'number' ? value.eligible : null, matched: typeof value.matched === 'number' ? value.matched : null, blocked: typeof value.blocked === 'number' ? value.blocked : null, unhealthy: typeof value.unhealthy === 'number' ? value.unhealthy : null, circuitTrips: value.circuitTrips, toolDistribution: isObject(value.toolDistribution) ? Object.fromEntries(Object.entries(value.toolDistribution).filter((entry) => typeof entry[1] === 'number')) : null };
1222
- }
1223
- function validateRuleCodeDecision(value) {
1224
- if (!isObject(value) || !isString(value.decisionId) || !isString(value.decision)
1225
- || !isObject(value.principal) || !isString(value.principal.kind)
1226
- || !isObject(value.authentication) || !isString(value.authentication.method)
1227
- || !isString(value.reasonCode) || (value.note !== null && !isString(value.note))
1228
- || !isString(value.decidedAt))
1229
- return null;
1230
- const actorId = typeof value.principal.ownerId === 'string'
1231
- ? value.principal.ownerId
1232
- : typeof value.principal.policyVersion === 'string'
1233
- ? value.principal.policyVersion
1234
- : typeof value.principal.reason === 'string' ? value.principal.reason : value.principal.kind;
1235
- return {
1236
- decisionId: value.decisionId,
1237
- decision: value.decision,
1238
- principalKind: value.principal.kind,
1239
- actorId,
1240
- authenticationMethod: value.authentication.method,
1241
- reasonCode: value.reasonCode,
1242
- note: typeof value.note === 'string' ? value.note : null,
1243
- decidedAt: value.decidedAt,
1244
- };
1245
- }
1246
- export function validateRuleCodeOwnerReview(v) {
1247
- if (!isObject(v) || !isObject(v.activation) || !isObject(v.artifact) || !isObject(v.readiness))
1248
- return null;
1249
- const { activation, artifact, readiness } = v;
1250
- if (!isString(activation.activationId) || !isString(activation.artifactId) || !isString(activation.action)
1251
- || !isString(artifact.artifactId) || !isString(artifact.digest)
1252
- || (artifact.content !== null && !isObject(artifact.content))
1253
- || !isString(readiness.status) || !['ready', 'evidence_insufficient', 'blocked', 'unavailable'].includes(readiness.status)
1254
- || !isString(readiness.evaluationId) || !Array.isArray(readiness.failedChecks)
1255
- || !readiness.failedChecks.every(item => isObject(item) && isString(item.checkId) && isString(item.reasonCode))
1256
- || !isObject(readiness.evidenceSnapshot) || !isString(readiness.evidenceSnapshot.snapshotDigest)
1257
- || !isObject(readiness.evidenceSnapshot.shadowSummary) || !Array.isArray(readiness.evidenceSnapshot.safetyGateResults)
1258
- || !readiness.evidenceSnapshot.safetyGateResults.every(item => isObject(item) && isString(item.checkId) && (item.status === 'passed' || item.status === 'failed') && (!Object.hasOwn(item, 'reasonCode') || isString(item.reasonCode))))
1259
- return null;
1260
- const summary = readiness.evidenceSnapshot.shadowSummary;
1261
- const summaryCounts = ['observed', 'matched', 'wouldBlock', 'wouldAllow', 'requireApproval', 'autoCorrect', 'errors', 'neutralControl'];
1262
- if (!summaryCounts.every(key => summary[key] === null || isNumber(summary[key])))
1263
- return null;
1264
- if (![summary.firstObservedAt, summary.lastObservedAt].every(value => value === null || isString(value)))
1265
- return null;
1266
- let controlState = null;
1267
- if (v.controlState !== null) {
1268
- if (!isObject(v.controlState) || (v.controlState.enforcement !== 'eligible' && v.controlState.enforcement !== 'safety_isolated') || !isNumber(v.controlState.version))
1269
- return null;
1270
- controlState = { enforcement: v.controlState.enforcement, version: v.controlState.version };
1271
- }
1272
- let globalPause = null;
1273
- if (v.globalPause !== null) {
1274
- if (!isObject(v.globalPause) || !isString(v.globalPause.pauseId) || (v.globalPause.status !== 'paused' && v.globalPause.status !== 'released') || !isNumber(v.globalPause.version))
1275
- return null;
1276
- globalPause = { pauseId: v.globalPause.pauseId, status: v.globalPause.status, version: v.globalPause.version };
1277
- }
1278
- if (!isBoolean(v.ownerDecisionEnabled))
1279
- return null;
1280
- if (!Array.isArray(v.decisions) || !isObject(v.runtimeCapability) || !isString(v.runtimeCapability.hostRuntimeVersion) || !isBoolean(v.runtimeCapability.shadowEvidence) || !isObject(v.liveMetrics) || !Array.isArray(v.liveMetrics.representativeSamples) || !v.liveMetrics.representativeSamples.every(sample => isObject(sample) && isString(sample.toolName) && isString(sample.decision) && isString(sample.pathCategory)) || !isObject(v.behaviorDrift))
1281
- return null;
1282
- const { behaviorDrift } = v;
1283
- const decisions = v.decisions.map(validateRuleCodeDecision);
1284
- if (decisions.some(decision => decision === null))
1285
- return null;
1286
- const driftKeys = ['approvedBlockRate', 'liveBlockRate', 'delta'];
1287
- if (!driftKeys.every(key => behaviorDrift[key] === null || isNumber(behaviorDrift[key])))
1288
- return null;
1289
- const last24Hours = validateTelemetryWindow(v.liveMetrics.last24Hours);
1290
- const last7Days = validateTelemetryWindow(v.liveMetrics.last7Days);
1291
- if (!last24Hours || !last7Days)
1292
- return null;
1293
- const readinessStatus = readiness.status === 'ready' || readiness.status === 'evidence_insufficient' || readiness.status === 'blocked' || readiness.status === 'unavailable' ? readiness.status : null;
1294
- const shadowSummary = {
1295
- observed: typeof summary.observed === 'number' ? summary.observed : null,
1296
- matched: typeof summary.matched === 'number' ? summary.matched : null,
1297
- wouldBlock: typeof summary.wouldBlock === 'number' ? summary.wouldBlock : null,
1298
- wouldAllow: typeof summary.wouldAllow === 'number' ? summary.wouldAllow : null,
1299
- requireApproval: typeof summary.requireApproval === 'number' ? summary.requireApproval : null,
1300
- autoCorrect: typeof summary.autoCorrect === 'number' ? summary.autoCorrect : null,
1301
- errors: typeof summary.errors === 'number' ? summary.errors : null,
1302
- neutralControl: typeof summary.neutralControl === 'number' ? summary.neutralControl : null,
1303
- firstObservedAt: typeof summary.firstObservedAt === 'string' ? summary.firstObservedAt : null,
1304
- lastObservedAt: typeof summary.lastObservedAt === 'string' ? summary.lastObservedAt : null,
1305
- };
1306
- if (readinessStatus === null)
1307
- return null;
1308
- return {
1309
- activation: { activationId: activation.activationId, artifactId: activation.artifactId, action: activation.action },
1310
- artifact: { artifactId: artifact.artifactId, digest: artifact.digest, content: artifact.content },
1311
- readiness: { status: readinessStatus, evaluationId: readiness.evaluationId, failedChecks: readiness.failedChecks.map(item => ({ checkId: item.checkId, reasonCode: item.reasonCode })), evidenceSnapshot: { snapshotDigest: readiness.evidenceSnapshot.snapshotDigest, shadowSummary, safetyGateResults: readiness.evidenceSnapshot.safetyGateResults.map(item => ({ checkId: item.checkId, status: item.status, ...(typeof item.reasonCode === 'string' ? { reasonCode: item.reasonCode } : {}) })) } },
1312
- controlState, globalPause, decisions: decisions.filter((decision) => decision !== null), ownerDecisionEnabled: v.ownerDecisionEnabled,
1313
- runtimeCapability: { hostRuntimeVersion: v.runtimeCapability.hostRuntimeVersion, shadowEvidence: v.runtimeCapability.shadowEvidence },
1314
- liveMetrics: { last24Hours, last7Days, representativeSamples: v.liveMetrics.representativeSamples.map(sample => ({ toolName: sample.toolName, decision: sample.decision, pathCategory: sample.pathCategory })) },
1315
- behaviorDrift: {
1316
- approvedBlockRate: typeof behaviorDrift.approvedBlockRate === 'number' ? behaviorDrift.approvedBlockRate : null,
1317
- liveBlockRate: typeof behaviorDrift.liveBlockRate === 'number' ? behaviorDrift.liveBlockRate : null,
1318
- delta: typeof behaviorDrift.delta === 'number' ? behaviorDrift.delta : null,
1319
- },
1320
- };
1321
- }
1322
- export function validateRuleCodeMutation(v) {
1323
- if (!isObject(v))
1324
- return null;
1325
- const result = {};
1326
- for (const key of ['decisionId', 'activationId', 'status', 'pauseId']) {
1327
- if (Object.hasOwn(v, key)) {
1328
- if (!isString(v[key]))
1329
- return null;
1330
- result[key] = v[key];
1331
- }
1332
- }
1333
- if (Object.hasOwn(v, 'version')) {
1334
- if (!isNumber(v.version))
1335
- return null;
1336
- result.version = v.version;
1337
- }
1338
- return result;
1339
- }
1340
- export function validateDisableActivation(v) {
1341
- if (!isObject(v))
1342
- return null;
1343
- if (!Object.hasOwn(v, 'activationId') || !isString(v.activationId))
1344
- return null;
1345
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1346
- return null;
1347
- return { activationId: v.activationId, status: v.status };
1348
- }
1349
- function validateLifecycleAdherence(v) {
1350
- if (!isObject(v))
1351
- return null;
1352
- if (!Object.hasOwn(v, 'insufficientData') || !isBoolean(v.insufficientData))
1353
- return null;
1354
- const rate = readNullableNumber(v, 'rate');
1355
- if (!rate.valid)
1356
- return null;
1357
- if (!Object.hasOwn(v, 'note') || !isString(v.note))
1358
- return null;
1359
- return {
1360
- insufficientData: v.insufficientData,
1361
- rate: rate.value,
1362
- note: v.note,
1363
- };
1364
- }
1365
- function validateLifecycleRuleMetric(v) {
1366
- if (!isObject(v))
1367
- return null;
1368
- if (!Object.hasOwn(v, 'ruleId') || !isString(v.ruleId))
1369
- return null;
1370
- if (!Object.hasOwn(v, 'triggered') || !isNumber(v.triggered))
1371
- return null;
1372
- const lastTriggeredAt = readNullableString(v, 'lastTriggeredAt');
1373
- if (!lastTriggeredAt.valid)
1374
- return null;
1375
- return {
1376
- ruleId: v.ruleId, triggered: v.triggered,
1377
- lastTriggeredAt: lastTriggeredAt.value,
1378
- };
1379
- }
1380
- export function validateLifecycleMetrics(v) {
1381
- if (!isObject(v))
1382
- return null;
1383
- if (!Object.hasOwn(v, 'principleId') || !isString(v.principleId))
1384
- return null;
1385
- if (!Object.hasOwn(v, 'adherence'))
1386
- return null;
1387
- const adherence = validateLifecycleAdherence(v.adherence);
1388
- if (adherence === null)
1389
- return null;
1390
- if (!Object.hasOwn(v, 'ruleMetrics') || !Array.isArray(v.ruleMetrics))
1391
- return null;
1392
- const ruleMetrics = validateArray(v.ruleMetrics, validateLifecycleRuleMetric);
1393
- if (ruleMetrics === null)
1394
- return null;
1395
- return { principleId: v.principleId, adherence, ruleMetrics };
1396
- }
1397
- const UPDATE_STATES = ['up_to_date', 'update_available', 'update_blocked', 'check_failed'];
1398
- export function validateUpdateStatus(v) {
1399
- if (!isObject(v))
1400
- return null;
1401
- if (!Object.hasOwn(v, 'currentVersion') || !isString(v.currentVersion))
1402
- return null;
1403
- if (!Object.hasOwn(v, 'latestVersion') || !isString(v.latestVersion))
1404
- return null;
1405
- if (!Object.hasOwn(v, 'hasUpdate') || !isBoolean(v.hasUpdate))
1406
- return null;
1407
- const result = {
1408
- currentVersion: v.currentVersion,
1409
- latestVersion: v.latestVersion,
1410
- hasUpdate: v.hasUpdate,
1411
- };
1412
- if (Object.hasOwn(v, 'state') && isString(v.state) && UPDATE_STATES.includes(v.state)) {
1413
- result.state = v.state;
1414
- }
1415
- if (Object.hasOwn(v, 'error') && isString(v.error)) {
1416
- result.error = v.error;
1417
- }
1418
- if (Object.hasOwn(v, 'message') && isString(v.message))
1419
- result.message = v.message;
1420
- if (Object.hasOwn(v, 'reason') && isString(v.reason))
1421
- result.reason = v.reason;
1422
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
1423
- result.nextAction = v.nextAction;
1424
- if (Object.hasOwn(v, 'versionSource') && (v.versionSource === 'active-release' || v.versionSource === 'plugin-package')) {
1425
- result.versionSource = v.versionSource;
1426
- }
1427
- if (Object.hasOwn(v, 'identityDivergence') && isObject(v.identityDivergence)) {
1428
- const divergence = v.identityDivergence;
1429
- if (Object.hasOwn(divergence, 'activeVersion') && isString(divergence.activeVersion)
1430
- && Object.hasOwn(divergence, 'pluginVersion') && isString(divergence.pluginVersion)
1431
- && Object.hasOwn(divergence, 'releaseId') && isString(divergence.releaseId)
1432
- && Object.hasOwn(divergence, 'generation') && typeof divergence.generation === 'number') {
1433
- result.identityDivergence = {
1434
- activeVersion: divergence.activeVersion,
1435
- pluginVersion: divergence.pluginVersion,
1436
- releaseId: divergence.releaseId,
1437
- generation: divergence.generation,
1438
- };
1439
- }
1440
- }
1441
- return result;
1442
- }
1443
- export const UPDATE_HISTORY_KINDS = [
1444
- 'update',
1445
- 'reinstall',
1446
- 'legacy_migration',
1447
- 'rollback',
1448
- 'refusal',
1449
- 'failure',
1450
- 'recovery',
1451
- 'unknown',
1452
- ];
1453
- function isUpdateHistoryKind(value) {
1454
- return typeof value === 'string' && UPDATE_HISTORY_KINDS.includes(value);
1455
- }
1456
- export function validateUpdateHistoryEntry(v) {
1457
- if (!isObject(v))
1458
- return null;
1459
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
1460
- return null;
1461
- if (!Object.hasOwn(v, 'timestamp') || !isString(v.timestamp))
1462
- return null;
1463
- if (!Object.hasOwn(v, 'fromVersion') || !isString(v.fromVersion))
1464
- return null;
1465
- if (!Object.hasOwn(v, 'toVersion') || !isString(v.toVersion))
1466
- return null;
1467
- if (!Object.hasOwn(v, 'success') || !isBoolean(v.success))
1468
- return null;
1469
- if (!Object.hasOwn(v, 'kind') || !isUpdateHistoryKind(v.kind))
1470
- return null;
1471
- const entry = {
1472
- id: v.id,
1473
- timestamp: v.timestamp,
1474
- fromVersion: v.fromVersion,
1475
- toVersion: v.toVersion,
1476
- success: v.success,
1477
- kind: v.kind,
1478
- };
1479
- if (Object.hasOwn(v, 'backupPath') && isString(v.backupPath)) {
1480
- entry.backupPath = v.backupPath;
1481
- }
1482
- if (Object.hasOwn(v, 'reason') && isString(v.reason)) {
1483
- entry.reason = v.reason;
1484
- }
1485
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction)) {
1486
- entry.nextAction = v.nextAction;
1487
- }
1488
- return entry;
1489
- }
1490
- export function validateUpdateHistory(v) {
1491
- // Backend returns a bare array; wrap it into { updates: [...] }.
1492
- if (Array.isArray(v)) {
1493
- const updates = validateArray(v, validateUpdateHistoryEntry);
1494
- if (updates === null)
1495
- return null;
1496
- return { updates };
1497
- }
1498
- // Also accept { updates: [...] } shape for forward-compat.
1499
- if (!isObject(v))
1500
- return null;
1501
- if (!Object.hasOwn(v, 'updates') || !Array.isArray(v.updates))
1502
- return null;
1503
- const updates = validateArray(v.updates, validateUpdateHistoryEntry);
1504
- if (updates === null)
1505
- return null;
1506
- return { updates };
1507
- }
1508
- function validateApprovalRecord(v) {
1509
- if (!isObject(v))
1510
- return null;
1511
- if (!Object.hasOwn(v, 'approvalId') || !isString(v.approvalId))
1512
- return null;
1513
- if (!Object.hasOwn(v, 'artifactId') || !isString(v.artifactId))
1514
- return null;
1515
- if (!Object.hasOwn(v, 'channel') || !isString(v.channel))
1516
- return null;
1517
- if (!Object.hasOwn(v, 'riskLevel') || !isString(v.riskLevel))
1518
- return null;
1519
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1520
- return null;
1521
- if (Object.hasOwn(v, 'confidence') && !isNumber(v.confidence))
1522
- return null;
1523
- if (!Object.hasOwn(v, 'requestedAt') || !isString(v.requestedAt))
1524
- return null;
1525
- const result = {
1526
- approvalId: v.approvalId, artifactId: v.artifactId, channel: v.channel,
1527
- riskLevel: v.riskLevel, status: v.status,
1528
- confidence: Object.hasOwn(v, 'confidence') && isNumber(v.confidence) ? v.confidence : undefined,
1529
- requestedAt: v.requestedAt,
1530
- decidedAt: Object.hasOwn(v, 'decidedAt') && isString(v.decidedAt) ? v.decidedAt : undefined,
1531
- decidedBy: Object.hasOwn(v, 'decidedBy') && isString(v.decidedBy) ? v.decidedBy : undefined,
1532
- decisionNote: Object.hasOwn(v, 'decisionNote') && isString(v.decisionNote) ? v.decisionNote : undefined,
1533
- rejectionReason: Object.hasOwn(v, 'rejectionReason') && isString(v.rejectionReason) ? v.rejectionReason : undefined,
1534
- summary: Object.hasOwn(v, 'summary') && isString(v.summary) ? v.summary : undefined,
1535
- triggerReason: Object.hasOwn(v, 'triggerReason') && isString(v.triggerReason) ? v.triggerReason : undefined,
1536
- confidenceLabel: Object.hasOwn(v, 'confidenceLabel') && isString(v.confidenceLabel) ? v.confidenceLabel : undefined,
1537
- confidenceExplanation: Object.hasOwn(v, 'confidenceExplanation') && isString(v.confidenceExplanation) ? v.confidenceExplanation : undefined,
1538
- effectDescription: Object.hasOwn(v, 'effectDescription') && isString(v.effectDescription) ? v.effectDescription : undefined,
1539
- rejectionEffect: Object.hasOwn(v, 'rejectionEffect') && isString(v.rejectionEffect) ? v.rejectionEffect : undefined,
1540
- };
1541
- if (Object.hasOwn(v, 'isMvpProven') && isBoolean(v.isMvpProven))
1542
- result.isMvpProven = v.isMvpProven;
1543
- if (Object.hasOwn(v, 'warning') && isString(v.warning))
1544
- result.warning = v.warning;
1545
- return result;
1546
- }
1547
- export function validateApprovalListResult(v) {
1548
- if (!isObject(v))
1549
- return null;
1550
- if (!Object.hasOwn(v, 'items') || !Array.isArray(v.items))
1551
- return null;
1552
- if (!Object.hasOwn(v, 'total') || !isNumber(v.total))
1553
- return null;
1554
- if (!Object.hasOwn(v, 'stats') || !isObject(v.stats))
1555
- return null;
1556
- const { stats } = v;
1557
- if (!Object.hasOwn(stats, 'pending') || !isNumber(stats.pending))
1558
- return null;
1559
- if (!Object.hasOwn(stats, 'approved') || !isNumber(stats.approved))
1560
- return null;
1561
- if (!Object.hasOwn(stats, 'rejected') || !isNumber(stats.rejected))
1562
- return null;
1563
- if (!Object.hasOwn(stats, 'cancelled') || !isNumber(stats.cancelled))
1564
- return null;
1565
- const items = validateArray(v.items, validateApprovalRecord);
1566
- if (items === null)
1567
- return null;
1568
- return { items, total: v.total, stats: { pending: stats.pending, approved: stats.approved, rejected: stats.rejected, cancelled: stats.cancelled } };
1569
- }
1570
- // Re-export the private validateApprovalRecord for direct use
1571
- export { validateApprovalRecord as validateApprovalRecordDirect };
1572
- const VALID_WARNING_CODES = ['technical_pattern', 'diagnostic_residue', 'title_too_long'];
1573
- function isReadabilityWarningCode(value) {
1574
- return isString(value) && VALID_WARNING_CODES.includes(value);
1575
- }
1576
- function validatePrincipleListItem(v) {
1577
- if (!isObject(v))
1578
- return null;
1579
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
1580
- return null;
1581
- if (!Object.hasOwn(v, 'text') || !isString(v.text))
1582
- return null;
1583
- if (!Object.hasOwn(v, 'triggerPattern') || !isString(v.triggerPattern))
1584
- return null;
1585
- if (!Object.hasOwn(v, 'action') || !isString(v.action))
1586
- return null;
1587
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1588
- return null;
1589
- if (!Object.hasOwn(v, 'priority') || !isString(v.priority))
1590
- return null;
1591
- if (!Object.hasOwn(v, 'scope') || !isString(v.scope))
1592
- return null;
1593
- const domain = readNullableString(v, 'domain');
1594
- if (!domain.valid)
1595
- return null;
1596
- if (!Object.hasOwn(v, 'evaluability') || !isString(v.evaluability))
1597
- return null;
1598
- if (!Object.hasOwn(v, 'valueScore') || !isNumber(v.valueScore))
1599
- return null;
1600
- if (!Object.hasOwn(v, 'adherenceRate') || !isNumber(v.adherenceRate))
1601
- return null;
1602
- if (!Object.hasOwn(v, 'painPreventedCount') || !isNumber(v.painPreventedCount))
1603
- return null;
1604
- if (!Object.hasOwn(v, 'ruleCount') || !isNumber(v.ruleCount))
1605
- return null;
1606
- if (!Object.hasOwn(v, 'conflictsWithCount') || !isNumber(v.conflictsWithCount))
1607
- return null;
1608
- if (!Object.hasOwn(v, 'createdAt') || !isString(v.createdAt))
1609
- return null;
1610
- if (!Object.hasOwn(v, 'updatedAt') || !isString(v.updatedAt))
1611
- return null;
1612
- const result = {
1613
- id: v.id, text: v.text, triggerPattern: v.triggerPattern, action: v.action,
1614
- status: v.status, priority: v.priority, scope: v.scope,
1615
- domain: domain.value,
1616
- evaluability: v.evaluability, valueScore: v.valueScore, adherenceRate: v.adherenceRate,
1617
- painPreventedCount: v.painPreventedCount, ruleCount: v.ruleCount,
1618
- conflictsWithCount: v.conflictsWithCount, createdAt: v.createdAt, updatedAt: v.updatedAt,
1619
- // PRI-332: detectedLanguage — absent defaults to 'unknown'; present but malformed → fail loud (ERR-009)
1620
- detectedLanguage: 'unknown',
1621
- };
1622
- if (Object.hasOwn(v, 'detectedLanguage')) {
1623
- if (!isString(v.detectedLanguage))
1624
- return null;
1625
- result.detectedLanguage = v.detectedLanguage;
1626
- }
1627
- // PRI-332 P1-5: readabilityWarningCode is optional — fail loud when present but invalid (ERR-009)
1628
- if (Object.hasOwn(v, 'readabilityWarningCode')) {
1629
- if (!isReadabilityWarningCode(v.readabilityWarningCode))
1630
- return null;
1631
- result.readabilityWarningCode = v.readabilityWarningCode;
1632
- }
1633
- // PRI-956 (F15): optional runtime-revocation disclosure — present only when
1634
- // the activations table reports a deactivated prompt activation.
1635
- if (Object.hasOwn(v, 'runtimeDeactivated')) {
1636
- if (v.runtimeDeactivated !== true)
1637
- return null;
1638
- result.runtimeDeactivated = true;
1639
- }
1640
- return result;
1641
- }
1642
- function validateCorePrinciple(v) {
1643
- if (!isObject(v))
1644
- return null;
1645
- if (!isString(v.id) || v.id.length === 0)
1646
- return null;
1647
- if (v.layer !== 'foundational' && v.layer !== 'operating')
1648
- return null;
1649
- if (!isString(v.name) || v.name.length === 0)
1650
- return null;
1651
- if (!isString(v.nameZh) || v.nameZh.length === 0)
1652
- return null;
1653
- if (!isString(v.statement) || v.statement.length === 0)
1654
- return null;
1655
- if (!isString(v.statementZh) || v.statementZh.length === 0)
1656
- return null;
1657
- return {
1658
- id: v.id,
1659
- layer: v.layer,
1660
- name: v.name,
1661
- nameZh: v.nameZh,
1662
- statement: v.statement,
1663
- statementZh: v.statementZh,
1664
- };
1665
- }
1666
- export function validateCorePrinciples(v) {
1667
- if (!isObject(v))
1668
- return null;
1669
- if (!Object.hasOwn(v, 'principles') || !Array.isArray(v.principles))
1670
- return null;
1671
- const principles = validateArray(v.principles, validateCorePrinciple);
1672
- if (principles === null)
1673
- return null;
1674
- return { principles };
1675
- }
1676
- export function validatePrinciplesList(v) {
1677
- if (!isObject(v))
1678
- return null;
1679
- if (!Object.hasOwn(v, 'principles') || !Array.isArray(v.principles))
1680
- return null;
1681
- if (!Object.hasOwn(v, 'summary') || !isObject(v.summary))
1682
- return null;
1683
- const s = v.summary;
1684
- const { candidate, probation, active, deprecated, archived, total } = s;
1685
- if (!isNumber(candidate) || !isNumber(probation) || !isNumber(active) || !isNumber(deprecated) || !isNumber(archived) || !isNumber(total))
1686
- return null;
1687
- const principles = validateArray(v.principles, validatePrincipleListItem);
1688
- if (principles === null)
1689
- return null;
1690
- // categories is optional (PRI-330) — EP-01: runtime validate, no `as` bypass
1691
- // When present but invalid, fail loud (ERR-009: required-ish fields fail loud)
1692
- let categories;
1693
- if (Object.hasOwn(v, 'categories')) {
1694
- if (!isObject(v.categories))
1695
- return null; // categories exists but is not an object → reject
1696
- const raw = v.categories;
1697
- const validated = {};
1698
- for (const [key, val] of Object.entries(raw)) {
1699
- if (!isNumber(val))
1700
- return null; // categories value is not a number → reject
1701
- validated[key] = val;
1702
- }
1703
- if (Object.keys(validated).length > 0) {
1704
- categories = validated;
1705
- }
1706
- }
1707
- // approvalCrossCheckUnavailable — optional string, fail loud if wrong type
1708
- // (ERR-009: required-ish fields fail loud; ERR-002: no silent degradation)
1709
- if (Object.hasOwn(v, 'approvalCrossCheckUnavailable') && !isString(v.approvalCrossCheckUnavailable)) {
1710
- return null;
1711
- }
1712
- const approvalCrossCheckUnavailable = isString(v.approvalCrossCheckUnavailable)
1713
- ? v.approvalCrossCheckUnavailable
1714
- : undefined;
1715
- // PRI-956 (F15, D3): revocationCheckUnavailable — optional string
1716
- if (Object.hasOwn(v, 'revocationCheckUnavailable') && !isString(v.revocationCheckUnavailable)) {
1717
- return null;
1718
- }
1719
- const revocationCheckUnavailable = isString(v.revocationCheckUnavailable)
1720
- ? v.revocationCheckUnavailable
1721
- : undefined;
1722
- return {
1723
- principles,
1724
- summary: { candidate, probation, active, deprecated, archived, total },
1725
- ...(categories !== undefined ? { categories } : {}),
1726
- ...(approvalCrossCheckUnavailable !== undefined ? { approvalCrossCheckUnavailable } : {}),
1727
- ...(revocationCheckUnavailable !== undefined ? { revocationCheckUnavailable } : {}),
1728
- };
1729
- }
1730
- function validateApprovalGroupRecord(v) {
1731
- if (!isObject(v))
1732
- return null;
1733
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
1734
- return null;
1735
- if (!Object.hasOwn(v, 'artifactId') || !isString(v.artifactId))
1736
- return null;
1737
- if (!Object.hasOwn(v, 'channel') || !isString(v.channel))
1738
- return null;
1739
- if (!Object.hasOwn(v, 'createdAt') || !isString(v.createdAt))
1740
- return null;
1741
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1742
- return null;
1743
- const record = { id: v.id, artifactId: v.artifactId, channel: v.channel, createdAt: v.createdAt, status: v.status };
1744
- if (Object.hasOwn(v, 'revisionReviewUnavailable')) {
1745
- if (typeof v.revisionReviewUnavailable !== 'boolean')
1746
- return null;
1747
- record.revisionReviewUnavailable = v.revisionReviewUnavailable;
1748
- }
1749
- return record;
1750
- }
1751
- function validateApprovalGroup(v) {
1752
- if (!isObject(v))
1753
- return null;
1754
- if (!Object.hasOwn(v, 'principleId') || !isString(v.principleId))
1755
- return null;
1756
- if (!Object.hasOwn(v, 'principleTitle') || !isString(v.principleTitle))
1757
- return null;
1758
- if (!Object.hasOwn(v, 'status') || !isString(v.status))
1759
- return null;
1760
- if (!Object.hasOwn(v, 'records') || !Array.isArray(v.records))
1761
- return null;
1762
- const records = validateArray(v.records, validateApprovalGroupRecord);
1763
- if (records === null)
1764
- return null;
1765
- const result = { principleId: v.principleId, principleTitle: v.principleTitle, status: v.status, records };
1766
- if (Object.hasOwn(v, 'revisionReviewUnavailable')) {
1767
- if (typeof v.revisionReviewUnavailable !== 'boolean')
1768
- return null;
1769
- result.revisionReviewUnavailable = v.revisionReviewUnavailable;
1770
- }
1771
- if (Object.hasOwn(v, 'revisionReview')) {
1772
- const review = v.revisionReview;
1773
- if (!isObject(review) || typeof review.oldStatement !== 'string' || typeof review.newStatement !== 'string' || !isObject(review.intentFields))
1774
- return null;
1775
- const intentFields = {};
1776
- for (const key of ['ownerIntent', 'targetBehavior', 'forbiddenBehavior', 'evidenceSource', 'validationExpectation']) {
1777
- const value = review.intentFields[key];
1778
- if (value !== undefined && typeof value !== 'string')
1779
- return null;
1780
- if (typeof value === 'string')
1781
- intentFields[key] = value;
1782
- }
1783
- result.revisionReview = { oldStatement: review.oldStatement, newStatement: review.newStatement, intentFields,
1784
- ...(typeof review.artifactId === 'string' ? { artifactId: review.artifactId } : {}),
1785
- ...(typeof review.artifactDigest === 'string' && /^sha256:[a-f0-9]{64}$/.test(review.artifactDigest) ? { artifactDigest: review.artifactDigest } : {}) };
1786
- }
1787
- // Wave 7: optional candidateDescription from artifact contentJson
1788
- if (Object.hasOwn(v, 'candidateDescription') && isString(v.candidateDescription) && v.candidateDescription.length > 0) {
1789
- result.candidateDescription = v.candidateDescription;
1790
- }
1791
- // PR-1894: optional fitsPromptBudget. Only a real boolean is propagated; a
1792
- // present-but-wrong value is dropped (rc-1/rc-2) so the badge falls back to
1793
- // copy that promises nothing rather than rendering an unverified claim.
1794
- if (Object.hasOwn(v, 'fitsPromptBudget') && typeof v.fitsPromptBudget === 'boolean') {
1795
- result.fitsPromptBudget = v.fitsPromptBudget;
1796
- }
1797
- // SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: optional tier fit, whitelist +
1798
- // drop-on-invalid (same rule). Absent keeps the boolean-only behavior.
1799
- if (Object.hasOwn(v, 'promptBudgetTierFit')
1800
- && (v.promptBudgetTierFit === 'fits_all_tiers' || v.promptBudgetTierFit === 'model_dependent'
1801
- || v.promptBudgetTierFit === 'exceeds_max_tier' || v.promptBudgetTierFit === 'unconfirmed')) {
1802
- result.promptBudgetTierFit = v.promptBudgetTierFit;
1803
- }
1804
- // PRI-940: optional artifactUnavailable. Only a real boolean is propagated; a
1805
- // present-but-wrong value is dropped so the card keeps its plain untitled
1806
- // fallback rather than rendering an unverified claim.
1807
- if (Object.hasOwn(v, 'artifactUnavailable') && typeof v.artifactUnavailable === 'boolean') {
1808
- result.artifactUnavailable = v.artifactUnavailable;
1809
- }
1810
- return result;
1811
- }
1812
- export function validatePromptInjectionBudgetStatus(v) {
1813
- if (!isObject(v))
1814
- return null;
1815
- if (!Object.hasOwn(v, 'budget') || !Object.hasOwn(v, 'usedChars') || !Object.hasOwn(v, 'truncated'))
1816
- return null;
1817
- const { budget, usedChars, truncated } = v;
1818
- if (typeof budget !== 'number' || !Number.isInteger(budget) || budget <= 0 ||
1819
- typeof usedChars !== 'number' || !Number.isInteger(usedChars) || usedChars < 0 ||
1820
- typeof truncated !== 'boolean') {
1821
- return null;
1822
- }
1823
- const result = { budget, usedChars, truncated };
1824
- // PR-1894: these are optional so a pre-PR-1894 server payload still
1825
- // validates (backward compatibility). A present-but-invalid value is
1826
- // DROPPED, never propagated (rc-1/rc-2): the UI then falls back to
1827
- // policy-agnostic copy rather than rendering an unverified policy claim.
1828
- if (Object.hasOwn(v, 'productionRotates') && typeof v.productionRotates === 'boolean') {
1829
- result.productionRotates = v.productionRotates;
1830
- }
1831
- if (Object.hasOwn(v, 'eligibleCount') && typeof v.eligibleCount === 'number' && Number.isInteger(v.eligibleCount) && v.eligibleCount >= 0) {
1832
- result.eligibleCount = v.eligibleCount;
1833
- }
1834
- // PD_PROMPT_CAPACITY_V1 (R-A1/R-A2/R-A3): additive optional fields, same
1835
- // drop-on-invalid rule. capacityStatus drives the 未确认 badge; without it
1836
- // the payload behaves as 'confirmed' (pre-SPEC server compat).
1837
- if (Object.hasOwn(v, 'capacityStatus') && (v.capacityStatus === 'confirmed' || v.capacityStatus === 'unconfirmed')) {
1838
- result.capacityStatus = v.capacityStatus;
1839
- }
1840
- if (Object.hasOwn(v, 'hostKind') && (v.hostKind === 'openclaw' || v.hostKind === 'codex' || v.hostKind === 'workbuddy')) {
1841
- result.hostKind = v.hostKind;
1842
- }
1843
- if (Object.hasOwn(v, 'route') && (v.route === 'legacy_trim' || v.route === 'shared_render')) {
1844
- result.route = v.route;
1845
- }
1846
- if (Object.hasOwn(v, 'unit') && v.unit === 'utf16_code_units') {
1847
- result.unit = v.unit;
1848
- }
1849
- if (Object.hasOwn(v, 'budgetScope') && (v.budgetScope === 'selected_lines' || v.budgetScope === 'full_directive_context')) {
1850
- result.budgetScope = v.budgetScope;
1851
- }
1852
- if (Object.hasOwn(v, 'fullRenderChars') && typeof v.fullRenderChars === 'number' && Number.isInteger(v.fullRenderChars) && v.fullRenderChars >= 0) {
1853
- result.fullRenderChars = v.fullRenderChars;
1854
- }
1855
- if (Object.hasOwn(v, 'oversizedActivationIds') && Array.isArray(v.oversizedActivationIds) && v.oversizedActivationIds.every(isString)) {
1856
- result.oversizedActivationIds = v.oversizedActivationIds;
1857
- }
1858
- if (Object.hasOwn(v, 'oversizedDiagnosticTruncated') && typeof v.oversizedDiagnosticTruncated === 'boolean') {
1859
- result.oversizedDiagnosticTruncated = v.oversizedDiagnosticTruncated;
1860
- }
1861
- if (Object.hasOwn(v, 'unconfirmedReason') && isString(v.unconfirmedReason)) {
1862
- result.unconfirmedReason = v.unconfirmedReason;
1863
- }
1864
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction)) {
1865
- result.nextAction = v.nextAction;
1866
- }
1867
- // SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §7: budget provenance, additive +
1868
- // drop-on-invalid. Absent behaves as the legacy fixed-budget view
1869
- // (pre-SPEC server compat).
1870
- if (Object.hasOwn(v, 'budgetCapacitySource') && isPromptBudgetCapacitySource(v.budgetCapacitySource)) {
1871
- result.budgetCapacitySource = v.budgetCapacitySource;
1872
- }
1873
- if (Object.hasOwn(v, 'budgetFallbackReason') && isPromptBudgetFallbackReason(v.budgetFallbackReason)) {
1874
- result.budgetFallbackReason = v.budgetFallbackReason;
1875
- }
1876
- if (Object.hasOwn(v, 'budgetCapacityStatus') && isPromptBudgetCapacityStatus(v.budgetCapacityStatus)) {
1877
- result.budgetCapacityStatus = v.budgetCapacityStatus;
1878
- }
1879
- if (Object.hasOwn(v, 'budgetContextWindowTokens') && typeof v.budgetContextWindowTokens === 'number' && Number.isInteger(v.budgetContextWindowTokens) && v.budgetContextWindowTokens > 0) {
1880
- result.budgetContextWindowTokens = v.budgetContextWindowTokens;
1881
- }
1882
- // PRI-1017: observation timestamp — a PAST runtime fact, never a live
1883
- // claim. Present-but-invalid drops (rc-1/rc-2), never propagated.
1884
- if (Object.hasOwn(v, 'budgetObservedAt') && isString(v.budgetObservedAt) && v.budgetObservedAt.length > 0 && Number.isFinite(Date.parse(v.budgetObservedAt))) {
1885
- result.budgetObservedAt = v.budgetObservedAt;
1886
- }
1887
- if (Object.hasOwn(v, 'perHost') && Array.isArray(v.perHost)) {
1888
- // rc-4: validate every element, not just "is array".
1889
- const perHost = [];
1890
- for (const entry of v.perHost) {
1891
- if (!isObject(entry))
1892
- return null;
1893
- const { hostKind, route, usedChars: hostUsed, truncated: hostTruncated } = entry;
1894
- if ((hostKind !== 'openclaw' && hostKind !== 'codex' && hostKind !== 'workbuddy')
1895
- || (route !== 'legacy_trim' && route !== 'shared_render')
1896
- || typeof hostUsed !== 'number' || !Number.isInteger(hostUsed) || hostUsed < 0
1897
- || typeof hostTruncated !== 'boolean') {
1898
- return null;
1899
- }
1900
- const forecast = { hostKind, route, usedChars: hostUsed, truncated: hostTruncated };
1901
- // PRI-1017: per-host budget basis (additive, drop-on-invalid). Absent
1902
- // behaves as the pre-PRI-1017 payload where the headline budget was
1903
- // shared — the UI must not present one host's window as another's.
1904
- if (Object.hasOwn(entry, 'budget') && typeof entry.budget === 'number' && Number.isInteger(entry.budget) && entry.budget > 0) {
1905
- forecast.budget = entry.budget;
1906
- }
1907
- if (Object.hasOwn(entry, 'budgetCapacitySource') && isPromptBudgetCapacitySource(entry.budgetCapacitySource)) {
1908
- forecast.budgetCapacitySource = entry.budgetCapacitySource;
1909
- }
1910
- if (Object.hasOwn(entry, 'budgetCapacityStatus') && isPromptBudgetCapacityStatus(entry.budgetCapacityStatus)) {
1911
- forecast.budgetCapacityStatus = entry.budgetCapacityStatus;
1912
- }
1913
- if (Object.hasOwn(entry, 'budgetObservedAt') && isString(entry.budgetObservedAt) && entry.budgetObservedAt.length > 0 && Number.isFinite(Date.parse(entry.budgetObservedAt))) {
1914
- forecast.budgetObservedAt = entry.budgetObservedAt;
1915
- }
1916
- if (Object.hasOwn(entry, 'budgetFallbackReason') && isPromptBudgetFallbackReason(entry.budgetFallbackReason)) {
1917
- forecast.budgetFallbackReason = entry.budgetFallbackReason;
1918
- }
1919
- perHost.push(forecast);
1920
- }
1921
- result.perHost = perHost;
1922
- }
1923
- return result;
1924
- }
1925
- export function validateApprovalsGrouped(v) {
1926
- if (!isObject(v))
1927
- return null;
1928
- if (!Object.hasOwn(v, 'groups') || !Array.isArray(v.groups))
1929
- return null;
1930
- if (!Object.hasOwn(v, 'generatedAt') || !isString(v.generatedAt))
1931
- return null;
1932
- const groups = validateArray(v.groups, validateApprovalGroup);
1933
- if (groups === null)
1934
- return null;
1935
- const result = { groups, generatedAt: v.generatedAt };
1936
- if (Object.hasOwn(v, 'note') && isString(v.note))
1937
- result.note = v.note;
1938
- // PRI-908: additive optional field — malformed/absent forecast degrades to
1939
- // "no badge" (the approve-time warning from PRI-890 remains the fail-loud
1940
- // exclusion report), never to a rejected payload.
1941
- if (Object.hasOwn(v, 'promptInjection')) {
1942
- const promptInjection = validatePromptInjectionBudgetStatus(v.promptInjection);
1943
- if (promptInjection !== null)
1944
- result.promptInjection = promptInjection;
1945
- }
1946
- return result;
1947
- }
1948
- // ── Evidence Chain (PRI-331) ──────────────────────────────────────────────────
1949
- const VALID_EVIDENCE_CHAIN_STATES = new Set([
1950
- 'recorded-only',
1951
- 'evidence-only',
1952
- 'diagnosis-queued',
1953
- 'diagnosis-running',
1954
- 'diagnosis-succeeded',
1955
- 'diagnosis-failed',
1956
- 'diagnosis-retry-wait',
1957
- 'candidate-generated',
1958
- 'internalization-missing',
1959
- 'internalization-pending',
1960
- 'internalization-running',
1961
- 'internalization-failed',
1962
- 'internalization-succeeded',
1963
- 'owner-reviewable',
1964
- 'malformed',
1965
- 'degraded',
1966
- ]);
1967
- // ── Intent Tension (PRI-469, SPEC §16) ───────────────────────────────────────
1968
- //
1969
- // Frontend mirror of the core IntentTension type. `confidence` is forbidden
1970
- // (SPEC §16.3) — validateIntentTension rejects any object carrying it.
1971
- // This validator is the Console's trust boundary: untrusted JSON from the API
1972
- // is narrowed here before any UI code touches it (ERR-001).
1973
- const VALID_INTENT_TENSION_SOURCES = ['none', 'action_drift', 'intent_suspect', 'healthy_tension'];
1974
- const VALID_EVIDENCE_STRENGTHS = ['weak', 'moderate', 'strong'];
1975
- const VALID_INTENT_RELATED_FIELDS = [
1976
- 'why',
1977
- 'desired_outcome',
1978
- 'non_negotiables',
1979
- 'stop_escalation',
1980
- 'current_strategic_focus',
1981
- ];
1982
- const VALID_SUGGESTED_OWNER_ACTIONS = [
1983
- 'confirm_drift',
1984
- 'revise_intent',
1985
- 'observe',
1986
- 'dismiss',
1987
- 'promote_to_principle',
1988
- 'promote_to_rulehost',
1989
- ];
1990
- /**
1991
- * Validate an untrusted `intentTension` object from the EvidenceChain API
1992
- * response. Returns `null` when malformed; the caller (validateEvidenceChainRecord)
1993
- * treats a `null` intentTension as "not present" so the record is still usable.
1994
- *
1995
- * SPEC §16.3: `confidence` is forbidden — explicitly rejected here.
1996
- * SPEC §16.4: evidence is capped at 3 items; we accept up to 3 and reject
1997
- * arrays with non-string elements (ERR-005/007).
1998
- */
1999
- export function validateIntentTension(v) {
2000
- if (!isObject(v))
2001
- return null;
2002
- // SPEC §16.3: confidence is forbidden on intentTension.
2003
- if (Object.hasOwn(v, 'confidence'))
2004
- return null;
2005
- // Required fields (ERR-009: fail loud when missing or wrong type)
2006
- if (!Object.hasOwn(v, 'source') || !isString(v.source))
2007
- return null;
2008
- if (!VALID_INTENT_TENSION_SOURCES.includes(v.source))
2009
- return null;
2010
- if (!Object.hasOwn(v, 'evidenceStrength') || !isString(v.evidenceStrength))
2011
- return null;
2012
- if (!VALID_EVIDENCE_STRENGTHS.includes(v.evidenceStrength))
2013
- return null;
2014
- if (!Object.hasOwn(v, 'relatedIntentFields') || !Array.isArray(v.relatedIntentFields))
2015
- return null;
2016
- const relatedIntentFields = [];
2017
- for (const f of v.relatedIntentFields) {
2018
- if (typeof f !== 'string')
2019
- return null;
2020
- if (!VALID_INTENT_RELATED_FIELDS.includes(f))
2021
- return null;
2022
- relatedIntentFields.push(f);
2023
- }
2024
- if (!Object.hasOwn(v, 'evidence') || !Array.isArray(v.evidence))
2025
- return null;
2026
- const evidence = [];
2027
- for (const e of v.evidence) {
2028
- if (typeof e !== 'string')
2029
- return null;
2030
- evidence.push(e);
2031
- }
2032
- // SPEC §16.4: cap at 3. Server-side already truncates, but the frontend
2033
- // must not trust that — enforce the cap here too (defense in depth).
2034
- const truncatedEvidence = evidence.slice(0, 3);
2035
- if (!Object.hasOwn(v, 'explanation') || !isString(v.explanation))
2036
- return null;
2037
- if (v.explanation.length === 0)
2038
- return null;
2039
- if (!Object.hasOwn(v, 'suggestedOwnerAction') || !isString(v.suggestedOwnerAction))
2040
- return null;
2041
- if (!VALID_SUGGESTED_OWNER_ACTIONS.includes(v.suggestedOwnerAction))
2042
- return null;
2043
- const result = {
2044
- source: v.source,
2045
- evidenceStrength: v.evidenceStrength,
2046
- relatedIntentFields,
2047
- evidence: truncatedEvidence,
2048
- explanation: v.explanation,
2049
- suggestedOwnerAction: v.suggestedOwnerAction,
2050
- };
2051
- // Optional: intentDocHash
2052
- if (Object.hasOwn(v, 'intentDocHash')) {
2053
- if (!isString(v.intentDocHash))
2054
- return null;
2055
- result.intentDocHash = v.intentDocHash;
2056
- }
2057
- return result;
2058
- }
2059
- /**
2060
- * Validate an untrusted IntentDecisionRecord object from the API.
2061
- * Returns null when malformed (Rule 3: fail loud on missing/wrong-type fields).
2062
- *
2063
- * Enum fields (source, evidenceStrength, ownerAction) are validated against
2064
- * the same sets used by validateIntentTension. relatedIntentFields elements
2065
- * are validated against VALID_INTENT_RELATED_FIELDS.
2066
- */
2067
- export function validateIntentDecisionRecord(v) {
2068
- if (!isObject(v))
2069
- return null;
2070
- // Required fields (ERR-009: fail loud when missing or wrong type)
2071
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
2072
- return null;
2073
- if (!Object.hasOwn(v, 'source') || !isString(v.source))
2074
- return null;
2075
- if (!VALID_INTENT_TENSION_SOURCES.includes(v.source))
2076
- return null;
2077
- if (!Object.hasOwn(v, 'evidenceStrength') || !isString(v.evidenceStrength))
2078
- return null;
2079
- if (!VALID_EVIDENCE_STRENGTHS.includes(v.evidenceStrength))
2080
- return null;
2081
- if (!Object.hasOwn(v, 'relatedIntentFields') || !Array.isArray(v.relatedIntentFields))
2082
- return null;
2083
- const relatedIntentFields = [];
2084
- for (const f of v.relatedIntentFields) {
2085
- if (typeof f !== 'string')
2086
- return null;
2087
- if (!VALID_INTENT_RELATED_FIELDS.includes(f))
2088
- return null;
2089
- relatedIntentFields.push(f);
2090
- }
2091
- if (!Object.hasOwn(v, 'ownerAction') || !isString(v.ownerAction))
2092
- return null;
2093
- if (!VALID_SUGGESTED_OWNER_ACTIONS.includes(v.ownerAction))
2094
- return null;
2095
- if (!Object.hasOwn(v, 'evidenceRefs') || !Array.isArray(v.evidenceRefs))
2096
- return null;
2097
- const evidenceRefs = [];
2098
- for (const e of v.evidenceRefs) {
2099
- if (typeof e !== 'string')
2100
- return null;
2101
- evidenceRefs.push(e);
2102
- }
2103
- if (!Object.hasOwn(v, 'createdAt') || !isString(v.createdAt))
2104
- return null;
2105
- const result = {
2106
- id: v.id,
2107
- source: v.source,
2108
- evidenceStrength: v.evidenceStrength,
2109
- relatedIntentFields,
2110
- ownerAction: v.ownerAction,
2111
- evidenceRefs,
2112
- createdAt: v.createdAt,
2113
- };
2114
- // Optional fields — fail loud when present but wrong type (ERR-009)
2115
- if (Object.hasOwn(v, 'painId')) {
2116
- if (!isString(v.painId))
2117
- return null;
2118
- result.painId = v.painId;
2119
- }
2120
- if (Object.hasOwn(v, 'taskId')) {
2121
- if (!isString(v.taskId))
2122
- return null;
2123
- result.taskId = v.taskId;
2124
- }
2125
- if (Object.hasOwn(v, 'runId')) {
2126
- if (!isString(v.runId))
2127
- return null;
2128
- result.runId = v.runId;
2129
- }
2130
- if (Object.hasOwn(v, 'intentDocHash')) {
2131
- if (!isString(v.intentDocHash))
2132
- return null;
2133
- result.intentDocHash = v.intentDocHash;
2134
- }
2135
- if (Object.hasOwn(v, 'resultingCandidateId')) {
2136
- if (!isString(v.resultingCandidateId))
2137
- return null;
2138
- result.resultingCandidateId = v.resultingCandidateId;
2139
- }
2140
- if (Object.hasOwn(v, 'resultingRuleCandidateId')) {
2141
- if (!isString(v.resultingRuleCandidateId))
2142
- return null;
2143
- result.resultingRuleCandidateId = v.resultingRuleCandidateId;
2144
- }
2145
- if (Object.hasOwn(v, 'patchProposalId')) {
2146
- if (!isString(v.patchProposalId))
2147
- return null;
2148
- result.patchProposalId = v.patchProposalId;
2149
- }
2150
- return result;
2151
- }
2152
- /**
2153
- * Validate a list of IntentDecisionRecord objects. Returns null if any
2154
- * element is malformed (ERR-005/007: validate array element types).
2155
- */
2156
- export function validateIntentDecisionList(v) {
2157
- return validateArray(v, validateIntentDecisionRecord);
2158
- }
2159
- /**
2160
- * Validate the POST /api/v1/intent-decisions response envelope.
2161
- * Shape: { record: IntentDecisionRecord, created: boolean }
2162
- */
2163
- export function validateIntentDecisionResult(v) {
2164
- if (!isObject(v))
2165
- return null;
2166
- if (!Object.hasOwn(v, 'record'))
2167
- return null;
2168
- const record = validateIntentDecisionRecord(v.record);
2169
- if (record === null)
2170
- return null;
2171
- if (!Object.hasOwn(v, 'created') || !isBoolean(v.created))
2172
- return null;
2173
- return { record, created: v.created };
2174
- }
2175
- /**
2176
- * Validate the GET /api/v1/intent-decisions/summary response.
2177
- * All 6 count keys must be present with non-negative finite numbers.
2178
- * lastDecisionAt: null is valid, string is valid, anything else returns null.
2179
- */
2180
- export function validateIntentDecisionSummary(v) {
2181
- if (!isObject(v))
2182
- return null;
2183
- if (!Object.hasOwn(v, 'counts') || !isObject(v.counts))
2184
- return null;
2185
- const { counts } = v;
2186
- // Explicit per-key checks so isNumber type guards narrow each property.
2187
- if (!Object.hasOwn(counts, 'confirm_drift') || !isNumber(counts.confirm_drift) || counts.confirm_drift < 0)
2188
- return null;
2189
- if (!Object.hasOwn(counts, 'revise_intent') || !isNumber(counts.revise_intent) || counts.revise_intent < 0)
2190
- return null;
2191
- if (!Object.hasOwn(counts, 'observe') || !isNumber(counts.observe) || counts.observe < 0)
2192
- return null;
2193
- if (!Object.hasOwn(counts, 'dismiss') || !isNumber(counts.dismiss) || counts.dismiss < 0)
2194
- return null;
2195
- if (!Object.hasOwn(counts, 'promote_to_principle') || !isNumber(counts.promote_to_principle) || counts.promote_to_principle < 0)
2196
- return null;
2197
- if (!Object.hasOwn(counts, 'promote_to_rulehost') || !isNumber(counts.promote_to_rulehost) || counts.promote_to_rulehost < 0)
2198
- return null;
2199
- // lastDecisionAt: null or string
2200
- if (!Object.hasOwn(v, 'lastDecisionAt'))
2201
- return null;
2202
- const { lastDecisionAt } = v;
2203
- if (lastDecisionAt !== null && !isString(lastDecisionAt))
2204
- return null;
2205
- return {
2206
- counts: {
2207
- confirm_drift: counts.confirm_drift,
2208
- revise_intent: counts.revise_intent,
2209
- observe: counts.observe,
2210
- dismiss: counts.dismiss,
2211
- promote_to_principle: counts.promote_to_principle,
2212
- promote_to_rulehost: counts.promote_to_rulehost,
2213
- },
2214
- lastDecisionAt,
2215
- };
2216
- }
2217
- const FOLLOW_UP_RESPONSE_TYPES = new Set([
2218
- 'link_candidate',
2219
- 'guide_rulehost',
2220
- 'generate_patch_proposal',
2221
- ]);
2222
- /**
2223
- * Validate the POST /api/v1/intent-decisions/:id/follow-up response.
2224
- *
2225
- * The response is a discriminated union on the `type` field. Each branch has
2226
- * its own required fields (Rule 3: fail loud on missing required fields).
2227
- *
2228
- * Common required fields: `type`, `decisionId`.
2229
- * - link_candidate: + `record` (IntentDecisionRecordData), `linkedCandidateId`
2230
- * - guide_rulehost: + `cliCommand`, `note`
2231
- * - generate_patch_proposal: + `record`, `patchProposal: { id, markdown }`
2232
- */
2233
- export function validateFollowUpResponse(v) {
2234
- if (!isObject(v))
2235
- return null;
2236
- if (!Object.hasOwn(v, 'type') || !isString(v.type))
2237
- return null;
2238
- if (!FOLLOW_UP_RESPONSE_TYPES.has(v.type))
2239
- return null;
2240
- if (!Object.hasOwn(v, 'decisionId') || !isString(v.decisionId))
2241
- return null;
2242
- const typedType = v.type;
2243
- const { decisionId } = v;
2244
- if (typedType === 'link_candidate') {
2245
- if (!Object.hasOwn(v, 'record'))
2246
- return null;
2247
- const record = validateIntentDecisionRecord(v.record);
2248
- if (record === null)
2249
- return null;
2250
- if (!Object.hasOwn(v, 'linkedCandidateId') || !isString(v.linkedCandidateId))
2251
- return null;
2252
- return { type: 'link_candidate', decisionId, record, linkedCandidateId: v.linkedCandidateId };
2253
- }
2254
- if (typedType === 'guide_rulehost') {
2255
- if (!Object.hasOwn(v, 'cliCommand') || !isString(v.cliCommand))
2256
- return null;
2257
- if (!Object.hasOwn(v, 'note') || !isString(v.note))
2258
- return null;
2259
- return { type: 'guide_rulehost', decisionId, cliCommand: v.cliCommand, note: v.note };
2260
- }
2261
- // generate_patch_proposal
2262
- if (!Object.hasOwn(v, 'record'))
2263
- return null;
2264
- const record = validateIntentDecisionRecord(v.record);
2265
- if (record === null)
2266
- return null;
2267
- if (!Object.hasOwn(v, 'patchProposal') || !isObject(v.patchProposal))
2268
- return null;
2269
- const pp = v.patchProposal;
2270
- if (!Object.hasOwn(pp, 'id') || !isString(pp.id))
2271
- return null;
2272
- if (!Object.hasOwn(pp, 'markdown') || !isString(pp.markdown))
2273
- return null;
2274
- return {
2275
- type: 'generate_patch_proposal',
2276
- decisionId,
2277
- record,
2278
- patchProposal: { id: pp.id, markdown: pp.markdown },
2279
- };
2280
- }
2281
- function validateEvidenceChainRecord(v) {
2282
- if (!isObject(v))
2283
- return null;
2284
- // Required fields (ERR-009: fail loud when missing)
2285
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
2286
- return null;
2287
- if (!Object.hasOwn(v, 'sourceKind') || !isString(v.sourceKind))
2288
- return null;
2289
- if (!Object.hasOwn(v, 'observedAt') || !isString(v.observedAt))
2290
- return null;
2291
- if (!Object.hasOwn(v, 'state') || !isString(v.state))
2292
- return null;
2293
- if (!VALID_EVIDENCE_CHAIN_STATES.has(v.state))
2294
- return null;
2295
- if (!Object.hasOwn(v, 'summary') || !isString(v.summary))
2296
- return null;
2297
- const result = {
2298
- id: v.id,
2299
- sourceKind: v.sourceKind,
2300
- observedAt: v.observedAt,
2301
- state: v.state,
2302
- summary: v.summary,
2303
- };
2304
- // Optional fields — fail loud when present but wrong type (ERR-009)
2305
- if (Object.hasOwn(v, 'hostKind')) {
2306
- if (!isString(v.hostKind))
2307
- return null;
2308
- if (v.hostKind !== 'openclaw' && v.hostKind !== 'codex' && v.hostKind !== 'unknown')
2309
- return null;
2310
- result.hostKind = v.hostKind;
2311
- }
2312
- if (Object.hasOwn(v, 'admissionDecision')) {
2313
- if (!isString(v.admissionDecision))
2314
- return null;
2315
- result.admissionDecision = v.admissionDecision;
2316
- }
2317
- if (Object.hasOwn(v, 'linkedPainId')) {
2318
- if (!isString(v.linkedPainId))
2319
- return null;
2320
- result.linkedPainId = v.linkedPainId;
2321
- }
2322
- if (Object.hasOwn(v, 'linkedTaskId')) {
2323
- if (!isString(v.linkedTaskId))
2324
- return null;
2325
- result.linkedTaskId = v.linkedTaskId;
2326
- }
2327
- if (Object.hasOwn(v, 'linkedTaskStatus')) {
2328
- if (!isString(v.linkedTaskStatus))
2329
- return null;
2330
- result.linkedTaskStatus = v.linkedTaskStatus;
2331
- }
2332
- if (Object.hasOwn(v, 'linkedCandidateId')) {
2333
- if (!isString(v.linkedCandidateId))
2334
- return null;
2335
- result.linkedCandidateId = v.linkedCandidateId;
2336
- }
2337
- if (Object.hasOwn(v, 'linkedPrincipleId')) {
2338
- if (!isString(v.linkedPrincipleId))
2339
- return null;
2340
- result.linkedPrincipleId = v.linkedPrincipleId;
2341
- }
2342
- if (Object.hasOwn(v, 'failureReason')) {
2343
- if (!isString(v.failureReason))
2344
- return null;
2345
- result.failureReason = v.failureReason;
2346
- }
2347
- if (Object.hasOwn(v, 'degradedReason')) {
2348
- if (!isString(v.degradedReason))
2349
- return null;
2350
- result.degradedReason = v.degradedReason;
2351
- }
2352
- if (Object.hasOwn(v, 'nextAction')) {
2353
- if (!isString(v.nextAction))
2354
- return null;
2355
- result.nextAction = v.nextAction;
2356
- }
2357
- // PRI-340: human-readable evidence fields (all optional)
2358
- if (Object.hasOwn(v, 'candidateTitle')) {
2359
- if (!isString(v.candidateTitle))
2360
- return null;
2361
- result.candidateTitle = v.candidateTitle;
2362
- }
2363
- if (Object.hasOwn(v, 'candidateSummary')) {
2364
- if (!isString(v.candidateSummary))
2365
- return null;
2366
- result.candidateSummary = v.candidateSummary;
2367
- }
2368
- if (Object.hasOwn(v, 'rootCauseSummary')) {
2369
- if (!isString(v.rootCauseSummary))
2370
- return null;
2371
- result.rootCauseSummary = v.rootCauseSummary;
2372
- }
2373
- if (Object.hasOwn(v, 'confidence')) {
2374
- if (typeof v.confidence !== 'number' || !Number.isFinite(v.confidence))
2375
- return null;
2376
- result.confidence = v.confidence;
2377
- }
2378
- if (Object.hasOwn(v, 'recommendationKind')) {
2379
- if (!isString(v.recommendationKind))
2380
- return null;
2381
- result.recommendationKind = v.recommendationKind;
2382
- }
2383
- // PRI-380: internalization task linkage (all optional)
2384
- if (Object.hasOwn(v, 'internalizationTaskId')) {
2385
- if (!isString(v.internalizationTaskId))
2386
- return null;
2387
- result.internalizationTaskId = v.internalizationTaskId;
2388
- }
2389
- if (Object.hasOwn(v, 'dreamerTaskStatus')) {
2390
- if (!isString(v.dreamerTaskStatus))
2391
- return null;
2392
- result.dreamerTaskStatus = v.dreamerTaskStatus;
2393
- }
2394
- // PRI-469: intentTension (optional). When present but malformed, we DROP
2395
- // the intentTension field but keep the rest of the record (the Owner still
2396
- // sees the pain evidence). This matches the core's graceful-degradation
2397
- // behavior where a malformed intentTension is omitted rather than failing
2398
- // the entire record. The degradedReason on the record (if any) explains
2399
- // what happened.
2400
- if (Object.hasOwn(v, 'intentTension')) {
2401
- const tension = validateIntentTension(v.intentTension);
2402
- if (tension !== null) {
2403
- result.intentTension = tension;
2404
- }
2405
- // If tension === null, we intentionally do NOT return null here — the
2406
- // record is still valid, just without intentTension.
2407
- }
2408
- return result;
2409
- }
2410
- export function validateEvidenceChain(v) {
2411
- if (!isObject(v))
2412
- return null;
2413
- if (!Object.hasOwn(v, 'records') || !Array.isArray(v.records))
2414
- return null;
2415
- if (!Object.hasOwn(v, 'generatedAt') || !isString(v.generatedAt))
2416
- return null;
2417
- const records = validateArray(v.records, validateEvidenceChainRecord);
2418
- if (records === null)
2419
- return null;
2420
- const result = {
2421
- records,
2422
- generatedAt: v.generatedAt,
2423
- };
2424
- if (Object.hasOwn(v, 'degradedReason')) {
2425
- if (!isString(v.degradedReason))
2426
- return null;
2427
- result.degradedReason = v.degradedReason;
2428
- }
2429
- if (Object.hasOwn(v, 'nextAction')) {
2430
- if (!isString(v.nextAction))
2431
- return null;
2432
- result.nextAction = v.nextAction;
2433
- }
2434
- if (Object.hasOwn(v, 'note')) {
2435
- if (!isString(v.note))
2436
- return null;
2437
- result.note = v.note;
2438
- }
2439
- return result;
2440
- }
2441
- const VALID_OUTPUT_LANGUAGE_VALUES = ['zh-CN', 'en'];
2442
- export function validateOutputLanguage(v) {
2443
- if (!isObject(v))
2444
- return null;
2445
- if (!Object.hasOwn(v, 'outputLanguage') || !isString(v.outputLanguage))
2446
- return null;
2447
- if (!VALID_OUTPUT_LANGUAGE_VALUES.includes(v.outputLanguage))
2448
- return null;
2449
- if (!Object.hasOwn(v, 'source') || !isString(v.source))
2450
- return null;
2451
- return { outputLanguage: v.outputLanguage, source: v.source };
2452
- }
2453
- export function validateApplyUpdateResult(v) {
2454
- if (!isObject(v))
2455
- return null;
2456
- if (!Object.hasOwn(v, 'success') || typeof v.success !== 'boolean')
2457
- return null;
2458
- if (!Object.hasOwn(v, 'message') || !isString(v.message))
2459
- return null;
2460
- const result = {
2461
- success: v.success,
2462
- message: v.message,
2463
- };
2464
- if (Object.hasOwn(v, 'refusal') && typeof v.refusal === 'boolean') {
2465
- result.refusal = v.refusal;
2466
- }
2467
- if (Object.hasOwn(v, 'state') && isString(v.state)
2468
- && (v.state === 'update_blocked' || v.state === 'awaiting_restart' || v.state === 'update_in_progress')) {
2469
- result.state = v.state;
2470
- }
2471
- if (Object.hasOwn(v, 'transactionId') && isString(v.transactionId)) {
2472
- result.transactionId = v.transactionId;
2473
- }
2474
- if (Object.hasOwn(v, 'updatedFiles') && Array.isArray(v.updatedFiles)) {
2475
- result.updatedFiles = v.updatedFiles.filter((f) => typeof f === 'string');
2476
- }
2477
- if (Object.hasOwn(v, 'backupPath') && isString(v.backupPath)) {
2478
- result.backupPath = v.backupPath;
2479
- }
2480
- if (Object.hasOwn(v, 'newVersion') && isString(v.newVersion)) {
2481
- result.newVersion = v.newVersion;
2482
- }
2483
- if (Object.hasOwn(v, 'partialUpdate') && typeof v.partialUpdate === 'boolean') {
2484
- result.partialUpdate = v.partialUpdate;
2485
- }
2486
- if (Object.hasOwn(v, 'requiresRestart') && typeof v.requiresRestart === 'boolean') {
2487
- result.requiresRestart = v.requiresRestart;
2488
- }
2489
- if (Object.hasOwn(v, 'reason') && isString(v.reason)) {
2490
- result.reason = v.reason;
2491
- }
2492
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction)) {
2493
- result.nextAction = v.nextAction;
2494
- }
2495
- if (Object.hasOwn(v, 'gatewayNotice') && isString(v.gatewayNotice)) {
2496
- result.gatewayNotice = v.gatewayNotice;
2497
- }
2498
- return result;
2499
- }
2500
- export function validateUpdateRecovery(v) {
2501
- if (!isObject(v))
2502
- return null;
2503
- if (!Object.hasOwn(v, 'needsRecovery') || typeof v.needsRecovery !== 'boolean')
2504
- return null;
2505
- const unfinished = [];
2506
- if (Object.hasOwn(v, 'unfinished') && Array.isArray(v.unfinished)) {
2507
- for (const item of v.unfinished) {
2508
- if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
2509
- continue;
2510
- unfinished.push({
2511
- transactionId: item.transactionId,
2512
- lastState: Object.hasOwn(item, 'lastState') && isString(item.lastState) ? item.lastState : null,
2513
- lastAt: Object.hasOwn(item, 'lastAt') && isString(item.lastAt) ? item.lastAt : null,
2514
- productVersion: Object.hasOwn(item, 'productVersion') && isString(item.productVersion) ? item.productVersion : null,
2515
- tornTailDetected: Object.hasOwn(item, 'tornTailDetected') && typeof item.tornTailDetected === 'boolean' ? item.tornTailDetected : false,
2516
- });
2517
- }
2518
- }
2519
- const broken = [];
2520
- if (Object.hasOwn(v, 'broken') && Array.isArray(v.broken)) {
2521
- for (const item of v.broken) {
2522
- if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
2523
- continue;
2524
- if (!Object.hasOwn(item, 'reason') || !isString(item.reason))
2525
- continue;
2526
- broken.push({ transactionId: item.transactionId, reason: item.reason });
2527
- }
2528
- }
2529
- const superseded = [];
2530
- if (Object.hasOwn(v, 'superseded') && Array.isArray(v.superseded)) {
2531
- for (const item of v.superseded) {
2532
- if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
2533
- continue;
2534
- if (!Object.hasOwn(item, 'reason') || !isString(item.reason))
2535
- continue;
2536
- superseded.push({ transactionId: item.transactionId, reason: item.reason });
2537
- }
2538
- }
2539
- const recoveryRequired = [];
2540
- if (Object.hasOwn(v, 'recoveryRequired') && Array.isArray(v.recoveryRequired)) {
2541
- for (const item of v.recoveryRequired) {
2542
- if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
2543
- continue;
2544
- if (!Object.hasOwn(item, 'reason') || !isString(item.reason))
2545
- continue;
2546
- recoveryRequired.push({ transactionId: item.transactionId, reason: item.reason });
2547
- }
2548
- }
2549
- const result = { needsRecovery: v.needsRecovery, unfinished, broken, superseded, recoveryRequired };
2550
- if (Object.hasOwn(v, 'activeRecordUnreadable') && typeof v.activeRecordUnreadable === 'boolean') {
2551
- result.activeRecordUnreadable = v.activeRecordUnreadable;
2552
- }
2553
- if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
2554
- result.nextAction = v.nextAction;
2555
- return result;
2556
- }
2557
- export function validateUpdateTransaction(v) {
2558
- if (!isObject(v))
2559
- return null;
2560
- if (!Object.hasOwn(v, 'transactionId') || !isString(v.transactionId))
2561
- return null;
2562
- if (!Object.hasOwn(v, 'exists') || typeof v.exists !== 'boolean')
2563
- return null;
2564
- const result = {
2565
- transactionId: v.transactionId,
2566
- exists: v.exists,
2567
- lastState: Object.hasOwn(v, 'lastState') && isString(v.lastState) ? v.lastState : null,
2568
- terminal: Object.hasOwn(v, 'terminal') && typeof v.terminal === 'boolean' ? v.terminal : false,
2569
- tornTailDetected: Object.hasOwn(v, 'tornTailDetected') && typeof v.tornTailDetected === 'boolean' ? v.tornTailDetected : false,
2570
- startedAt: Object.hasOwn(v, 'startedAt') && isString(v.startedAt) ? v.startedAt : null,
2571
- lastAt: Object.hasOwn(v, 'lastAt') && isString(v.lastAt) ? v.lastAt : null,
2572
- productVersion: Object.hasOwn(v, 'productVersion') && isString(v.productVersion) ? v.productVersion : null,
2573
- reason: Object.hasOwn(v, 'reason') && isString(v.reason) ? v.reason : undefined,
2574
- };
2575
- return result;
2576
- }
2577
- // ── Principle Trajectory validators ─────────────────────────────────────────
2578
- const VALID_STAGE_KEYS = ['evidence', 'diagnosis', 'proposal', 'review', 'deploy', 'behavior'];
2579
- const VALID_STAGE_STATUSES = ['available', 'unavailable', 'not_applicable'];
2580
- function isStageKey(v) {
2581
- return isString(v) && VALID_STAGE_KEYS.includes(v);
2582
- }
2583
- function isStageStatus(v) {
2584
- return isString(v) && VALID_STAGE_STATUSES.includes(v);
2585
- }
2586
- function validateTrajectoryStage(v) {
2587
- if (!isObject(v))
2588
- return null;
2589
- if (!Object.hasOwn(v, 'key') || !isStageKey(v.key))
2590
- return null;
2591
- if (!Object.hasOwn(v, 'status') || !isStageStatus(v.status))
2592
- return null;
2593
- if (!Object.hasOwn(v, 'summary') || !isString(v.summary))
2594
- return null;
2595
- const result = {
2596
- key: v.key,
2597
- status: v.status,
2598
- summary: v.summary,
2599
- };
2600
- if (Object.hasOwn(v, 'detail')) {
2601
- if (!isString(v.detail))
2602
- return null;
2603
- result.detail = v.detail;
2604
- }
2605
- if (Object.hasOwn(v, 'timestamp')) {
2606
- if (!isString(v.timestamp))
2607
- return null;
2608
- result.timestamp = v.timestamp;
2609
- }
2610
- if (Object.hasOwn(v, 'unavailableReason')) {
2611
- if (!isString(v.unavailableReason))
2612
- return null;
2613
- result.unavailableReason = v.unavailableReason;
2614
- }
2615
- if (Object.hasOwn(v, 'nextAction')) {
2616
- if (!isString(v.nextAction))
2617
- return null;
2618
- result.nextAction = v.nextAction;
2619
- }
2620
- // meta is Record<string, unknown> — accept any object (ERR-008: bounded preview)
2621
- if (Object.hasOwn(v, 'meta')) {
2622
- if (!isObject(v.meta))
2623
- return null;
2624
- result.meta = v.meta;
2625
- }
2626
- return result;
2627
- }
2628
- export function validateTrajectoryData(v) {
2629
- if (!isObject(v))
2630
- return null;
2631
- if (!Object.hasOwn(v, 'principleId') || !isString(v.principleId))
2632
- return null;
2633
- if (!Object.hasOwn(v, 'stages') || !Array.isArray(v.stages))
2634
- return null;
2635
- const stages = validateArray(v.stages, validateTrajectoryStage);
2636
- if (stages === null)
2637
- return null;
2638
- const result = {
2639
- principleId: v.principleId,
2640
- stages,
2641
- };
2642
- if (Object.hasOwn(v, 'degraded')) {
2643
- if (!isObject(v.degraded))
2644
- return null;
2645
- const d = v.degraded;
2646
- if (!Object.hasOwn(d, 'reason') || !isString(d.reason))
2647
- return null;
2648
- if (!Object.hasOwn(d, 'nextAction') || !isString(d.nextAction))
2649
- return null;
2650
- result.degraded = { reason: d.reason, nextAction: d.nextAction };
2651
- }
2652
- return result;
2653
- }
2654
- // ── Intent Page (PRI-466) ────────────────────────────────────────────────────
2655
- const VALID_INTENT_REASONS = ['flag_disabled', 'not_found', 'read_error', 'parse_error', 'oversized'];
2656
- const VALID_INTENT_WARNING_CODES = ['missing_section', 'empty_section', 'too_vague', 'oversized', 'parse_failed'];
2657
- const VALID_INTENT_SECTION_KEYS = ['why', 'desiredOutcome', 'nonNegotiables', 'stopEscalation', 'currentStrategicFocus'];
2658
- export function validateIntentWarning(v) {
2659
- if (!isObject(v))
2660
- return null;
2661
- if (!Object.hasOwn(v, 'code') || !isString(v.code))
2662
- return null;
2663
- if (!VALID_INTENT_WARNING_CODES.includes(v.code))
2664
- return null;
2665
- if (!Object.hasOwn(v, 'message') || !isString(v.message))
2666
- return null;
2667
- const result = { code: v.code, message: v.message };
2668
- // Runtime Contract Rule #3: fail loud on wrong-type optional fields,
2669
- // consistent with validateIntentSummary's optional-field handling.
2670
- if (Object.hasOwn(v, 'section')) {
2671
- if (!isString(v.section))
2672
- return null;
2673
- result.section = v.section;
2674
- }
2675
- return result;
2676
- }
2677
- export function validateIntentSections(v) {
2678
- if (!isObject(v))
2679
- return null;
2680
- const result = {};
2681
- for (const key of VALID_INTENT_SECTION_KEYS) {
2682
- if (Object.hasOwn(v, key)) {
2683
- const val = v[key];
2684
- if (val !== null && !isString(val))
2685
- return null;
2686
- if (isString(val)) {
2687
- result[key] = val;
2688
- }
2689
- }
2690
- }
2691
- return result;
2692
- }
2693
- export function validateIntentSummary(v) {
2694
- if (!isObject(v))
2695
- return null;
2696
- // Required fields
2697
- if (!Object.hasOwn(v, 'ok') || !isBoolean(v.ok))
2698
- return null;
2699
- if (!Object.hasOwn(v, 'found') || !isBoolean(v.found))
2700
- return null;
2701
- if (!Object.hasOwn(v, 'flagEnabled') || !isBoolean(v.flagEnabled))
2702
- return null;
2703
- if (!Object.hasOwn(v, 'warnings') || !Array.isArray(v.warnings))
2704
- return null;
2705
- const warnings = validateArray(v.warnings, validateIntentWarning);
2706
- if (warnings === null)
2707
- return null;
2708
- const result = {
2709
- ok: v.ok,
2710
- found: v.found,
2711
- flagEnabled: v.flagEnabled,
2712
- warnings,
2713
- };
2714
- // Optional fields — validate type if present, fail loud on wrong type
2715
- if (Object.hasOwn(v, 'reason')) {
2716
- if (!isString(v.reason))
2717
- return null;
2718
- if (!VALID_INTENT_REASONS.includes(v.reason))
2719
- return null;
2720
- result.reason = v.reason;
2721
- }
2722
- if (Object.hasOwn(v, 'nextAction')) {
2723
- if (!isString(v.nextAction))
2724
- return null;
2725
- result.nextAction = v.nextAction;
2726
- }
2727
- if (Object.hasOwn(v, 'path')) {
2728
- if (!isString(v.path))
2729
- return null;
2730
- result.path = v.path;
2731
- }
2732
- if (Object.hasOwn(v, 'contentHash')) {
2733
- if (!isString(v.contentHash))
2734
- return null;
2735
- result.contentHash = v.contentHash;
2736
- }
2737
- if (Object.hasOwn(v, 'lastEditedAt')) {
2738
- if (!isString(v.lastEditedAt))
2739
- return null;
2740
- result.lastEditedAt = v.lastEditedAt;
2741
- }
2742
- if (Object.hasOwn(v, 'sections')) {
2743
- const sections = validateIntentSections(v.sections);
2744
- if (sections === null)
2745
- return null;
2746
- result.sections = sections;
2747
- }
2748
- return result;
2749
- }
2750
- export function validateIntentRawContent(v) {
2751
- if (!isObject(v))
2752
- return null;
2753
- if (!Object.hasOwn(v, 'content') || !isString(v.content))
2754
- return null;
2755
- if (!Object.hasOwn(v, 'path') || !isString(v.path))
2756
- return null;
2757
- return { content: v.content, path: v.path };
2758
- }
2759
- export function validateIntentInitResult(v) {
2760
- if (!isObject(v))
2761
- return null;
2762
- // Required fields
2763
- if (!Object.hasOwn(v, 'ok') || !isBoolean(v.ok))
2764
- return null;
2765
- if (!Object.hasOwn(v, 'created') || !isBoolean(v.created))
2766
- return null;
2767
- const result = {
2768
- ok: v.ok,
2769
- created: v.created,
2770
- };
2771
- // Optional fields
2772
- const path = readNullableString(v, 'path');
2773
- if (!path.valid)
2774
- return null;
2775
- if (path.value !== null)
2776
- result.path = path.value;
2777
- const reason = readNullableString(v, 'reason');
2778
- if (!reason.valid)
2779
- return null;
2780
- if (reason.value !== null)
2781
- result.reason = reason.value;
2782
- const nextAction = readNullableString(v, 'nextAction');
2783
- if (!nextAction.valid)
2784
- return null;
2785
- if (nextAction.value !== null)
2786
- result.nextAction = nextAction.value;
2787
- return result;
2788
- }
2789
- export function validateIntentSaveResult(v) {
2790
- if (!isObject(v))
2791
- return null;
2792
- // Required fields
2793
- if (!Object.hasOwn(v, 'ok') || !isBoolean(v.ok))
2794
- return null;
2795
- if (!Object.hasOwn(v, 'saved') || !isBoolean(v.saved))
2796
- return null;
2797
- const result = {
2798
- ok: v.ok,
2799
- saved: v.saved,
2800
- };
2801
- // Optional fields
2802
- const path = readNullableString(v, 'path');
2803
- if (!path.valid)
2804
- return null;
2805
- if (path.value !== null)
2806
- result.path = path.value;
2807
- const contentHash = readNullableString(v, 'contentHash');
2808
- if (!contentHash.valid)
2809
- return null;
2810
- if (contentHash.value !== null)
2811
- result.contentHash = contentHash.value;
2812
- const lastEditedAt = readNullableString(v, 'lastEditedAt');
2813
- if (!lastEditedAt.valid)
2814
- return null;
2815
- if (lastEditedAt.value !== null)
2816
- result.lastEditedAt = lastEditedAt.value;
2817
- const reason = readNullableString(v, 'reason');
2818
- if (!reason.valid)
2819
- return null;
2820
- if (reason.value !== null)
2821
- result.reason = reason.value;
2822
- const nextAction = readNullableString(v, 'nextAction');
2823
- if (!nextAction.valid)
2824
- return null;
2825
- if (nextAction.value !== null)
2826
- result.nextAction = nextAction.value;
2827
- if (Object.hasOwn(v, 'warnings')) {
2828
- if (!Array.isArray(v.warnings))
2829
- return null;
2830
- const warnings = validateArray(v.warnings, validateIntentWarning);
2831
- if (warnings === null)
2832
- return null;
2833
- result.warnings = warnings;
2834
- }
2835
- return result;
2836
- }
2837
- function validateIntentVersionEntry(v) {
2838
- if (!isObject(v))
2839
- return null;
2840
- // Required string fields — fail loud on missing or wrong type (rc-3, rc-4)
2841
- if (!Object.hasOwn(v, 'id') || !isString(v.id))
2842
- return null;
2843
- if (!Object.hasOwn(v, 'lang') || !isString(v.lang))
2844
- return null;
2845
- if (!Object.hasOwn(v, 'contentHash') || !isString(v.contentHash))
2846
- return null;
2847
- if (!Object.hasOwn(v, 'contentSnapshot') || !isString(v.contentSnapshot))
2848
- return null;
2849
- if (!Object.hasOwn(v, 'reason') || !isString(v.reason))
2850
- return null;
2851
- if (!Object.hasOwn(v, 'createdAt') || !isString(v.createdAt))
2852
- return null;
2853
- return {
2854
- id: v.id,
2855
- lang: v.lang,
2856
- contentHash: v.contentHash,
2857
- contentSnapshot: v.contentSnapshot,
2858
- reason: v.reason,
2859
- createdAt: v.createdAt,
2860
- };
2861
- }
2862
- export function validateIntentVersions(v) {
2863
- if (!isObject(v))
2864
- return null;
2865
- if (!Object.hasOwn(v, 'versions') || !Array.isArray(v.versions))
2866
- return null;
2867
- const versions = validateArray(v.versions, validateIntentVersionEntry);
2868
- if (versions === null)
2869
- return null;
2870
- return { versions };
2871
- }
2872
- function parseOwnerGovernanceReadiness(v) {
2873
- if (!isObject(v))
2874
- return null;
2875
- if (!Object.hasOwn(v, 'authenticationMode') || (v.authenticationMode !== 'authenticated' && v.authenticationMode !== 'no_auth'))
2876
- return null;
2877
- if (!Object.hasOwn(v, 'ownerIdentityConfiguration') || (v.ownerIdentityConfiguration !== 'configured' && v.ownerIdentityConfiguration !== 'missing' && v.ownerIdentityConfiguration !== 'invalid'))
2878
- return null;
2879
- return { authenticationMode: v.authenticationMode, ownerIdentityConfiguration: v.ownerIdentityConfiguration };
2880
- }
2881
- function parseOwnerIdentityRecordData(v) {
2882
- if (v === undefined || v === null)
2883
- return null;
2884
- if (!isObject(v))
2885
- return null;
2886
- const rec = v;
2887
- if (!Object.hasOwn(rec, 'schemaVersion') || !isNumber(rec.schemaVersion))
2888
- return null;
2889
- if (!Object.hasOwn(rec, 'ownerId') || !isString(rec.ownerId))
2890
- return null;
2891
- if (!Object.hasOwn(rec, 'credentialId') || !isString(rec.credentialId))
2892
- return null;
2893
- if (!Object.hasOwn(rec, 'registeredAt') || !isString(rec.registeredAt))
2894
- return null;
2895
- return { schemaVersion: rec.schemaVersion, ownerId: rec.ownerId, credentialId: rec.credentialId, registeredAt: rec.registeredAt };
2896
- }
2897
- export function validateOwnerIdentityView(v) {
2898
- if (!isObject(v))
2899
- return null;
2900
- if (!Object.hasOwn(v, 'resolved') || !isObject(v.resolved))
2901
- return null;
2902
- const r = v.resolved;
2903
- if (!Object.hasOwn(r, 'ownerId') || (r.ownerId !== null && !isString(r.ownerId)))
2904
- return null;
2905
- if (!Object.hasOwn(r, 'credentialId') || (r.credentialId !== null && !isString(r.credentialId)))
2906
- return null;
2907
- if (!Object.hasOwn(r, 'source') || (r.source !== 'env' && r.source !== 'file' && r.source !== 'none' && r.source !== 'invalid_env'))
2908
- return null;
2909
- const governance = parseOwnerGovernanceReadiness(v.governance);
2910
- if (governance === null)
2911
- return null;
2912
- const out = {
2913
- resolved: {
2914
- ownerId: r.ownerId,
2915
- credentialId: r.credentialId,
2916
- source: r.source,
2917
- ...(Object.hasOwn(r, 'error') && isString(r.error) ? { error: r.error } : {}),
2918
- },
2919
- fileRecord: parseOwnerIdentityRecordData(v.fileRecord),
2920
- governance,
2921
- };
2922
- if (Object.hasOwn(v, 'fileError') && isString(v.fileError))
2923
- out.fileError = v.fileError;
2924
- return out;
2925
- }
2926
- export function validateOwnerIdentityRegister(v) {
2927
- if (!isObject(v))
2928
- return null;
2929
- if (!Object.hasOwn(v, 'source') || v.source !== 'file')
2930
- return null;
2931
- if (!Object.hasOwn(v, 'record') || !isObject(v.record))
2932
- return null;
2933
- const rec = v.record;
2934
- if (!Object.hasOwn(rec, 'schemaVersion') || !isNumber(rec.schemaVersion))
2935
- return null;
2936
- if (!Object.hasOwn(rec, 'ownerId') || !isString(rec.ownerId))
2937
- return null;
2938
- if (!Object.hasOwn(rec, 'credentialId') || !isString(rec.credentialId))
2939
- return null;
2940
- if (!Object.hasOwn(rec, 'registeredAt') || !isString(rec.registeredAt))
2941
- return null;
2942
- const governance = parseOwnerGovernanceReadiness(v.governance);
2943
- if (governance === null)
2944
- return null;
2945
- return {
2946
- source: 'file',
2947
- record: { schemaVersion: rec.schemaVersion, ownerId: rec.ownerId, credentialId: rec.credentialId, registeredAt: rec.registeredAt },
2948
- governance,
2949
- };
2950
- }
2951
- export function validateOwnerIdentityUnregister(v) {
2952
- if (!isObject(v))
2953
- return null;
2954
- if (!Object.hasOwn(v, 'ok') || !isBoolean(v.ok) || v.ok !== true)
2955
- return null;
2956
- if (!Object.hasOwn(v, 'source') || v.source !== 'none')
2957
- return null;
2958
- const governance = parseOwnerGovernanceReadiness(v.governance);
2959
- if (governance === null)
2960
- return null;
2961
- return { ok: true, source: 'none', governance };
2962
- }
2963
- // ── PRI-629: unified Owner Decision inbox (owner-decisions API) ──────────────
2964
- const OWNER_DECISION_KINDS = new Set(['evaluator_review', 'rollout_review', 'activation_approval', 'rulecode_decision']);
2965
- const OWNER_DECISION_ACTIONS = new Set(['accept_current', 'revise_once', 'reject_current', 'approve', 'reject', 'promote', 'reject_after_shadow']);
2966
- /**
2967
- * PRI-858: strict shape check for the formation evidence block. Anything
2968
- * malformed → the caller omits the whole block (an evidence projection is a
2969
- * review aid; a partial one would misrepresent what the machine actually
2970
- * saw). Deliberately does NOT carry raw artifact payloads or full lineage
2971
- * arrays — only the bounded projection the Owner reads.
2972
- */
2973
- function validateOwnerFormationEvidence(value) {
2974
- if (!isObject(value))
2975
- return null;
2976
- const { provenance } = value;
2977
- if (!isString(value.version))
2978
- return null;
2979
- const sourcePainId = readNullableString(value, 'sourcePainId');
2980
- if (!sourcePainId.valid)
2981
- return null;
2982
- if (!Array.isArray(value.notes))
2983
- return null;
2984
- const notes = [];
2985
- for (const note of value.notes) {
2986
- if (!isString(note))
2987
- return null;
2988
- notes.push(note);
2989
- }
2990
- if (!isObject(provenance))
2991
- return null;
2992
- const dreamerId = readNullableString(provenance, 'sourceDreamerArtifactId');
2993
- if (!dreamerId.valid || dreamerId.value === null)
2994
- return null;
2995
- const diagnosisArtifactId = readNullableString(provenance, 'sourceDiagnosisArtifactId');
2996
- const diagnosisTaskId = readNullableString(provenance, 'sourceDiagnosisTaskId');
2997
- if (!diagnosisArtifactId.valid || !diagnosisTaskId.valid)
2998
- return null;
2999
- const parsed = {
3000
- version: value.version,
3001
- sourcePainId: sourcePainId.value,
3002
- provenance: {
3003
- sourceDreamerArtifactId: dreamerId.value,
3004
- sourceDiagnosisArtifactId: diagnosisArtifactId.value,
3005
- sourceDiagnosisTaskId: diagnosisTaskId.value,
3006
- },
3007
- notes,
3008
- };
3009
- if (value.diagnosis !== undefined) {
3010
- if (!isObject(value.diagnosis))
3011
- return null;
3012
- const { diagnosis } = value;
3013
- const rootCause = readNullableString(diagnosis, 'rootCause');
3014
- const summary = readNullableString(diagnosis, 'summary');
3015
- if (!rootCause.valid || !summary.valid)
3016
- return null;
3017
- if (!Array.isArray(diagnosis.evidence))
3018
- return null;
3019
- const evidenceRows = [];
3020
- for (const entry of diagnosis.evidence) {
3021
- if (!isObject(entry))
3022
- return null;
3023
- const sourceRef = readNullableString(entry, 'sourceRef');
3024
- const note = readNullableString(entry, 'note');
3025
- if (!sourceRef.valid || sourceRef.value === null || !note.valid || note.value === null)
3026
- return null;
3027
- evidenceRows.push({ sourceRef: sourceRef.value, note: note.value });
3028
- }
3029
- parsed.diagnosis = { rootCause: rootCause.value, summary: summary.value, evidence: evidenceRows };
3030
- }
3031
- return parsed;
3032
- }
3033
- function readOwnerStringArray(value) {
3034
- if (!Array.isArray(value) || value.some(entry => typeof entry !== 'string'))
3035
- return null;
3036
- return value;
3037
- }
3038
- function validateOwnerDecisionReview(value) {
3039
- if (!isObject(value) || !isObject(value.brief) || !isObject(value.evidence) || !isObject(value.capability))
3040
- return null;
3041
- const { brief, evidence, capability } = value;
3042
- if (brief.kind !== 'evaluator' && brief.kind !== 'rollout')
3043
- return null;
3044
- const requiredChanges = readOwnerStringArray(brief.requiredChanges);
3045
- if (requiredChanges === null)
3046
- return null;
3047
- const parsedBrief = { kind: brief.kind, requiredChanges };
3048
- if (brief.kind === 'evaluator') {
3049
- if (!isObject(brief.principle) || !isObject(brief.implementation))
3050
- return null;
3051
- const scope = readOwnerStringArray(brief.principle.scope);
3052
- const affectedTools = readOwnerStringArray(brief.implementation.affectedTools);
3053
- const risks = readOwnerStringArray(brief.implementation.risks);
3054
- const strengths = readOwnerStringArray(brief.strengths);
3055
- const concerns = readOwnerStringArray(brief.concerns);
3056
- if (scope === null || affectedTools === null || risks === null || strengths === null || concerns === null)
3057
- return null;
3058
- parsedBrief.principle = { scope };
3059
- for (const key of ['title', 'statement', 'rationale']) {
3060
- const candidate = brief.principle[key];
3061
- if (candidate !== undefined && !isString(candidate))
3062
- return null;
3063
- if (isString(candidate))
3064
- parsedBrief.principle[key] = candidate;
3065
- }
3066
- parsedBrief.implementation = { affectedTools, risks };
3067
- if (brief.implementation.summary !== undefined && !isString(brief.implementation.summary))
3068
- return null;
3069
- if (isString(brief.implementation.summary))
3070
- parsedBrief.implementation.summary = brief.implementation.summary;
3071
- parsedBrief.strengths = strengths;
3072
- parsedBrief.concerns = concerns;
3073
- if (brief.score !== undefined && !isNumber(brief.score))
3074
- return null;
3075
- if (isNumber(brief.score))
3076
- parsedBrief.score = brief.score;
3077
- // PRI-704 / PRI-703 Phase 4 (评审修正): optional quality checklist —
3078
- // strict five-item contract: schemaVersion must be exactly 1 and the five
3079
- // known item ids must each appear EXACTLY once (the UI builds i18n keys
3080
- // from the id; an unknown id would render an untranslated label, a
3081
- // missing id an incomplete checklist). Anything else → omit the whole
3082
- // checklist (older snapshots legitimately lack it; a partial one must
3083
- // not masquerade as a review aid).
3084
- if (brief.qualityChecklist !== undefined) {
3085
- const KNOWN_IDS = ['understandability', 'evidence', 'actionability', 'generalization', 'boundary'];
3086
- const qc = brief.qualityChecklist;
3087
- if (isObject(qc) && qc.schemaVersion === 1 && Array.isArray(qc.items)) {
3088
- const validItems = [];
3089
- let shapeValid = true;
3090
- const seen = new Set();
3091
- for (const entry of qc.items) {
3092
- if (!isObject(entry) || !isString(entry.id)
3093
- || typeof entry.pass !== 'boolean' || !isString(entry.note)) {
3094
- shapeValid = false;
3095
- break;
3096
- }
3097
- seen.add(entry.id);
3098
- validItems.push({ id: entry.id, pass: entry.pass, note: entry.note });
3099
- }
3100
- const allKnownOnce = shapeValid
3101
- && validItems.length === KNOWN_IDS.length
3102
- && KNOWN_IDS.every((id) => seen.has(id));
3103
- if (allKnownOnce) {
3104
- parsedBrief.qualityChecklist = { schemaVersion: 1, items: validItems };
3105
- }
3106
- }
3107
- }
3108
- // PRI-858: formation evidence projection (optional, display-only). Absent
3109
- // on legacy snapshots; malformed → omitted rather than half-rendered.
3110
- if (brief.formationEvidence !== undefined) {
3111
- const formationEvidence = validateOwnerFormationEvidence(brief.formationEvidence);
3112
- if (formationEvidence !== null)
3113
- parsedBrief.formationEvidence = formationEvidence;
3114
- }
3115
- }
3116
- else {
3117
- const risks = readOwnerStringArray(brief.risks);
3118
- if (risks === null)
3119
- return null;
3120
- if (brief.summary !== undefined && !isString(brief.summary))
3121
- return null;
3122
- if (isString(brief.summary))
3123
- parsedBrief.summary = brief.summary;
3124
- parsedBrief.risks = risks;
3125
- }
3126
- if (evidence.completeness !== 'complete' && evidence.completeness !== 'partial' && evidence.completeness !== 'insufficient')
3127
- return null;
3128
- if (!isString(evidence.digest) || !Array.isArray(evidence.deterministicChecks) || !Array.isArray(evidence.items))
3129
- return null;
3130
- const deterministicChecks = [];
3131
- for (const entry of evidence.deterministicChecks) {
3132
- if (!isObject(entry) || !isString(entry.check)
3133
- || (entry.status !== 'passed' && entry.status !== 'failed' && entry.status !== 'not_run' && entry.status !== 'unavailable'))
3134
- return null;
3135
- deterministicChecks.push({ check: entry.check, status: entry.status });
3136
- }
3137
- const items = [];
3138
- for (const entry of evidence.items) {
3139
- if (!isObject(entry) || !isString(entry.evidenceClass) || !isString(entry.label) || !isString(entry.value))
3140
- return null;
3141
- items.push({ evidenceClass: entry.evidenceClass, label: entry.label, value: entry.value });
3142
- }
3143
- if (!isObject(capability.acceptRequirement))
3144
- return null;
3145
- const requirement = capability.acceptRequirement;
3146
- if (requirement.kind !== 'none' && requirement.kind !== 'acknowledge_partial_evidence' && requirement.kind !== 'forbidden')
3147
- return null;
3148
- if (requirement.reasonCode !== undefined && !isString(requirement.reasonCode))
3149
- return null;
3150
- return {
3151
- brief: parsedBrief,
3152
- evidence: { completeness: evidence.completeness, deterministicChecks, items, digest: evidence.digest },
3153
- capability: { acceptRequirement: { kind: requirement.kind, ...(isString(requirement.reasonCode) ? { reasonCode: requirement.reasonCode } : {}) } },
3154
- };
3155
- }
3156
- export function validateOwnerDecisionItem(v) {
3157
- if (!isObject(v))
3158
- return null;
3159
- const { reviewKey } = v;
3160
- const { kind } = v;
3161
- const { taskId } = v;
3162
- const { title } = v;
3163
- const { summary } = v;
3164
- const { reasonCode } = v;
3165
- const { createdAt } = v;
3166
- if (!isString(reviewKey) || !isString(kind) || !isString(taskId) || !isString(title)
3167
- || !isString(summary) || !isString(reasonCode) || !isString(createdAt))
3168
- return null;
3169
- if (!OWNER_DECISION_KINDS.has(kind))
3170
- return null;
3171
- if (!Object.hasOwn(v, 'legacy') || !isBoolean(v.legacy))
3172
- return null;
3173
- if (!Object.hasOwn(v, 'allowedActions') || !Array.isArray(v.allowedActions))
3174
- return null;
3175
- for (const action of v.allowedActions) {
3176
- if (typeof action !== 'string' || !OWNER_DECISION_ACTIONS.has(action))
3177
- return null;
3178
- }
3179
- if (!Object.hasOwn(v, 'expectedRevisionEpoch') || !isNumber(v.expectedRevisionEpoch))
3180
- return null;
3181
- const { expectedRevisionEpoch } = v;
3182
- const { expectedSourceRunId } = v;
3183
- const { expectedSourceArtifactId } = v;
3184
- const { expectedSourceArtifactHash } = v;
3185
- if (!isString(expectedSourceRunId) || !isString(expectedSourceArtifactId) || !isString(expectedSourceArtifactHash))
3186
- return null;
3187
- const item = {
3188
- reviewKey,
3189
- kind,
3190
- taskId,
3191
- title,
3192
- summary,
3193
- reasonCode,
3194
- legacy: v.legacy,
3195
- allowedActions: v.allowedActions,
3196
- expectedRevisionEpoch,
3197
- expectedSourceRunId,
3198
- expectedSourceArtifactId,
3199
- expectedSourceArtifactHash,
3200
- createdAt,
3201
- };
3202
- if (Object.hasOwn(v, 'expectedEvidenceDigest')) {
3203
- if (!isString(v.expectedEvidenceDigest))
3204
- return null;
3205
- item.expectedEvidenceDigest = v.expectedEvidenceDigest;
3206
- }
3207
- if (Object.hasOwn(v, 'review')) {
3208
- const review = validateOwnerDecisionReview(v.review);
3209
- if (review === null)
3210
- return null;
3211
- if (item.expectedEvidenceDigest !== undefined && item.expectedEvidenceDigest !== review.evidence.digest)
3212
- return null;
3213
- item.review = review;
3214
- }
3215
- if (Object.hasOwn(v, 'evidenceUnavailableReason')) {
3216
- if (!isString(v.evidenceUnavailableReason) || v.evidenceUnavailableReason.trim() === '')
3217
- return null;
3218
- item.evidenceUnavailableReason = v.evidenceUnavailableReason;
3219
- }
3220
- if ((kind === 'evaluator_review' || kind === 'rollout_review')
3221
- && item.evidenceUnavailableReason === undefined
3222
- && (item.expectedEvidenceDigest === undefined || item.review === undefined))
3223
- return null;
3224
- if (Object.hasOwn(v, 'machineRecommendation')) {
3225
- if (!isString(v.machineRecommendation))
3226
- return null;
3227
- item.machineRecommendation = v.machineRecommendation;
3228
- }
3229
- if (Object.hasOwn(v, 'score')) {
3230
- if (!isNumber(v.score))
3231
- return null;
3232
- item.score = v.score;
3233
- }
3234
- if (Object.hasOwn(v, 'principleId')) {
3235
- if (!isString(v.principleId))
3236
- return null;
3237
- item.principleId = v.principleId;
3238
- }
3239
- return item;
3240
- }
3241
- export function validateOwnerDecisionsData(v) {
3242
- if (!isObject(v))
3243
- return null;
3244
- if (!Object.hasOwn(v, 'items') || !Array.isArray(v.items))
3245
- return null;
3246
- const items = [];
3247
- for (const entry of v.items) {
3248
- const item = validateOwnerDecisionItem(entry);
3249
- if (item === null)
3250
- return null;
3251
- items.push(item);
3252
- }
3253
- if (!Object.hasOwn(v, 'total') || !isNumber(v.total))
3254
- return null;
3255
- if (!Object.hasOwn(v, 'generatedAt') || !isString(v.generatedAt))
3256
- return null;
3257
- return { items, total: v.total, generatedAt: v.generatedAt };
3258
- }
3259
- export function validateOwnerResolutionResult(v) {
3260
- if (!isObject(v))
3261
- return null;
3262
- if (!Object.hasOwn(v, 'status') || v.status !== 'resolved')
3263
- return null;
3264
- const { resolutionId } = v;
3265
- const { reviewKey } = v;
3266
- const { action } = v;
3267
- if (!isString(resolutionId) || !isString(reviewKey) || !isString(action))
3268
- return null;
3269
- if (!Object.hasOwn(v, 'applied') || !isBoolean(v.applied))
3270
- return null;
3271
- if (!Object.hasOwn(v, 'runnerWillApply') || !isBoolean(v.runnerWillApply))
3272
- return null;
3273
- const result = {
3274
- status: 'resolved',
3275
- resolutionId,
3276
- reviewKey,
3277
- action,
3278
- applied: v.applied,
3279
- runnerWillApply: v.runnerWillApply,
3280
- };
3281
- if (Object.hasOwn(v, 'effectiveDecision')) {
3282
- if (!isString(v.effectiveDecision))
3283
- return null;
3284
- result.effectiveDecision = v.effectiveDecision;
3285
- }
3286
- if (Object.hasOwn(v, 'targetTaskId')) {
3287
- if (!isString(v.targetTaskId))
3288
- return null;
3289
- result.targetTaskId = v.targetTaskId;
3290
- }
3291
- if (Object.hasOwn(v, 'nextAction')) {
3292
- if (!isString(v.nextAction))
3293
- return null;
3294
- result.nextAction = v.nextAction;
3295
- }
3296
- return result;
3297
- }