@resolveio/server-lib 22.3.195 → 22.3.197

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 (745) hide show
  1. package/.nodemon.json +5 -0
  2. package/.vscode/settings.json +21 -0
  3. package/AGENTS.md +195 -0
  4. package/README.md +22 -0
  5. package/build_package.sh +5 -0
  6. package/compileDTS.pl +64 -0
  7. package/docs/ai-assistant-nightly-eval.md +65 -0
  8. package/docs/ai-assistant-preflight-checklist.md +23 -0
  9. package/docs/ai-assistant-report-builder-bridge-playbook.md +115 -0
  10. package/eslint-plugin-custom/index.js +7 -0
  11. package/eslint-plugin-custom/rules/no-filter-zero-index.js +44 -0
  12. package/eslint.config.js +103 -0
  13. package/gulpfile.js +216 -0
  14. package/methodAndPublicationListGenerator.py +375 -0
  15. package/mongodbensurers.js +2 -0
  16. package/mongostop.js +3 -0
  17. package/package.json +1 -1
  18. package/scripts/cleanup-bypassed-callmethod-logs.js +616 -0
  19. package/settings.development.json +25 -0
  20. package/settings.development.redacted.json +25 -0
  21. package/src/.env +12 -0
  22. package/src/ai/assistant-core-heuristics.ts +379 -0
  23. package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +185 -0
  24. package/src/ai/resolveio-platform-intelligence-memory.ts +325 -0
  25. package/{ai/resolveio-platform-intelligence-types.d.ts → src/ai/resolveio-platform-intelligence-types.ts} +20 -15
  26. package/src/ai/resolveio-platform-intelligence.ts +462 -0
  27. package/src/client-server-app.ts +12 -0
  28. package/src/collections/ai-run.collection.ts +117 -0
  29. package/src/collections/ai-terminal-conversation.collection.ts +91 -0
  30. package/src/collections/ai-terminal-issue-report.collection.ts +99 -0
  31. package/src/collections/ai-terminal-message.collection.ts +77 -0
  32. package/src/collections/app-setting.collection.ts +104 -0
  33. package/src/collections/app-status.collection.ts +58 -0
  34. package/src/collections/communication-metric.collection.ts +84 -0
  35. package/src/collections/counter.collection.ts +56 -0
  36. package/src/collections/cron-job-history.collection.ts +94 -0
  37. package/src/collections/cron-job.collection.ts +92 -0
  38. package/src/collections/customer-notification.collection.ts +131 -0
  39. package/src/collections/customer-portal-password.collection.ts +76 -0
  40. package/src/collections/email-history.collection.ts +134 -0
  41. package/src/collections/email-verified.collection.ts +62 -0
  42. package/src/collections/file.collection.ts +74 -0
  43. package/src/collections/flag-update.collection.ts +57 -0
  44. package/src/collections/flag.collection.ts +57 -0
  45. package/src/collections/log-method-latency.collection.ts +77 -0
  46. package/src/collections/log-subscription.collection.ts +80 -0
  47. package/src/collections/log.collection.ts +93 -0
  48. package/src/collections/logged-in-users.collection.ts +67 -0
  49. package/src/collections/monitor-cpu.collection.ts +65 -0
  50. package/src/collections/monitor-function.collection.ts +74 -0
  51. package/src/collections/monitor-memory.collection.ts +77 -0
  52. package/src/collections/monitor-mongo.collection.ts +71 -0
  53. package/src/collections/notification.collection.ts +57 -0
  54. package/src/collections/openai-usage-ledger.collection.ts +131 -0
  55. package/src/collections/report-builder-dashboard-builder.collection.ts +109 -0
  56. package/src/collections/report-builder-library.collection.ts +89 -0
  57. package/src/collections/report-builder-report.collection.ts +184 -0
  58. package/src/collections/user-group.collection.ts +89 -0
  59. package/src/collections/user-guide.collection.ts +57 -0
  60. package/src/collections/user.collection.ts +181 -0
  61. package/src/cron/cron.ts +117 -0
  62. package/src/fixtures/cron-jobs.ts +95 -0
  63. package/src/fixtures/init.ts +35 -0
  64. package/src/http/auth.ts +818 -0
  65. package/src/http/health.ts +7 -0
  66. package/src/http/home.ts +90 -0
  67. package/src/http/slow-query-publication.ts +49 -0
  68. package/src/index.ts +1 -0
  69. package/src/managers/ai-assistant-codex-manager.manager.ts +1131 -0
  70. package/src/managers/ai-run-evidence.manager.ts +264 -0
  71. package/src/managers/communication-metric.manager.ts +82 -0
  72. package/src/managers/cron.manager.ts +333 -0
  73. package/src/managers/customer-notification-content.manager.ts +236 -0
  74. package/src/managers/diagnostic-manager-bootstrap.ts +165 -0
  75. package/src/managers/error-auto-fix.manager.ts +2767 -0
  76. package/src/managers/local-log.manager.ts +113 -0
  77. package/src/managers/method.manager.ts +1857 -0
  78. package/src/managers/mongo.manager.ts +4575 -0
  79. package/src/managers/monitor.manager.ts +507 -0
  80. package/src/managers/openai-usage-ledger.manager.ts +112 -0
  81. package/src/managers/slow-query-verifier.manager.ts +3590 -0
  82. package/src/managers/slow-query.manager.ts +519 -0
  83. package/src/managers/subscription.manager.ts +3128 -0
  84. package/src/managers/websocket.manager.ts +746 -0
  85. package/src/managers/worker-dispatcher.manager.ts +1360 -0
  86. package/src/managers/worker-server.manager.ts +536 -0
  87. package/src/methods/accounts.ts +532 -0
  88. package/src/methods/ai-terminal.ts +23825 -0
  89. package/src/methods/app-settings.ts +114 -0
  90. package/src/methods/aws.ts +649 -0
  91. package/src/methods/collections.ts +641 -0
  92. package/src/methods/counters.ts +69 -0
  93. package/src/methods/cron-jobs.ts +2614 -0
  94. package/src/methods/customer-notifications.ts +458 -0
  95. package/src/methods/diagnostics.ts +616 -0
  96. package/src/methods/flag-updates.ts +7 -0
  97. package/src/methods/flags.ts +7 -0
  98. package/src/methods/logs.ts +657 -0
  99. package/src/methods/mongo-explorer.ts +1880 -0
  100. package/src/methods/monitor.ts +540 -0
  101. package/src/methods/pdf.ts +1236 -0
  102. package/src/methods/publications.ts +129 -0
  103. package/src/methods/report-builder.ts +3300 -0
  104. package/src/methods/support.ts +335 -0
  105. package/src/models/ai-run.model.ts +27 -0
  106. package/src/models/ai-terminal-conversation.model.ts +19 -0
  107. package/src/models/ai-terminal-issue-report.model.ts +21 -0
  108. package/src/models/ai-terminal-message.model.ts +24 -0
  109. package/src/models/app-setting.model.ts +17 -0
  110. package/{models/app-status.model.d.ts → src/models/app-status.model.ts} +3 -2
  111. package/{models/billing-logged-in-users.model.d.ts → src/models/billing-logged-in-users.model.ts} +5 -4
  112. package/src/models/collection-document.model.ts +24 -0
  113. package/src/models/communication-metric.model.ts +23 -0
  114. package/{models/counter.model.d.ts → src/models/counter.model.ts} +4 -3
  115. package/src/models/cron-job-history.model.ts +16 -0
  116. package/src/models/cron-job.model.ts +15 -0
  117. package/src/models/customer-notification.model.ts +28 -0
  118. package/src/models/customer-portal-password.model.ts +12 -0
  119. package/src/models/dialog.model.ts +25 -0
  120. package/{models/email-history.model.js → src/models/email-history.model.ts} +36 -4
  121. package/{models/email-verified.model.d.ts → src/models/email-verified.model.ts} +6 -5
  122. package/{models/file.model.d.ts → src/models/file.model.ts} +8 -7
  123. package/{models/flag-update.model.d.ts → src/models/flag-update.model.ts} +4 -3
  124. package/{models/flag.model.d.ts → src/models/flag.model.ts} +4 -3
  125. package/src/models/log-method-latency.model.ts +11 -0
  126. package/{models/log-subscription.model.d.ts → src/models/log-subscription.model.ts} +11 -9
  127. package/src/models/log.model.ts +19 -0
  128. package/{models/logged-in-users.model.d.ts → src/models/logged-in-users.model.ts} +6 -5
  129. package/{models/method-response.model.d.ts → src/models/method-response.model.ts} +7 -6
  130. package/src/models/method.model.ts +25 -0
  131. package/{models/monitor-cpu.model.d.ts → src/models/monitor-cpu.model.ts} +9 -7
  132. package/src/models/monitor-function.model.ts +16 -0
  133. package/src/models/monitor-memory.model.ts +17 -0
  134. package/src/models/monitor-mongo.model.ts +15 -0
  135. package/{models/notification.model.d.ts → src/models/notification.model.ts} +6 -4
  136. package/src/models/openai-usage-ledger.model.ts +56 -0
  137. package/src/models/pagination.model.ts +35 -0
  138. package/src/models/permission.model.ts +14 -0
  139. package/src/models/report-builder-dashboard-builder.model.ts +29 -0
  140. package/src/models/report-builder-library.model.ts +20 -0
  141. package/src/models/report-builder-report.model.ts +136 -0
  142. package/src/models/report-builder.model.ts +68 -0
  143. package/src/models/select-data-label.model.ts +9 -0
  144. package/src/models/server-message.model.ts +31 -0
  145. package/src/models/slow-query-report.model.ts +23 -0
  146. package/src/models/subscription.model.ts +73 -0
  147. package/src/models/support-ticket.model.ts +104 -0
  148. package/src/models/user-group.model.ts +24 -0
  149. package/{models/user-guide.model.d.ts → src/models/user-guide.model.ts} +5 -4
  150. package/src/models/user.model.ts +96 -0
  151. package/src/private/images/ResolveIO.png +0 -0
  152. package/src/publications/ai-terminal.ts +73 -0
  153. package/src/publications/app-settings.ts +25 -0
  154. package/src/publications/app-status.ts +13 -0
  155. package/src/publications/cron-jobs.ts +40 -0
  156. package/src/publications/customer-notifications.ts +101 -0
  157. package/src/publications/files.ts +33 -0
  158. package/src/publications/flags-update.ts +19 -0
  159. package/src/publications/flags.ts +19 -0
  160. package/src/publications/logs.ts +163 -0
  161. package/src/publications/notifications.ts +13 -0
  162. package/src/publications/report-builder-dashboard-builders.ts +39 -0
  163. package/src/publications/report-builder-libraries.ts +41 -0
  164. package/src/publications/report-builder-reports.ts +47 -0
  165. package/src/publications/super-admin.ts +13 -0
  166. package/src/publications/user-groups.ts +12 -0
  167. package/src/publications/user-guides.ts +12 -0
  168. package/src/resolveio-server-app.ts +617 -0
  169. package/src/server-app.ts +3354 -0
  170. package/src/services/codex-client.ts +1231 -0
  171. package/src/services/openai-client.ts +265 -0
  172. package/src/types/error-report.ts +26 -0
  173. package/src/types/js-tiktoken.d.ts +11 -0
  174. package/src/types/slow-query-report.ts +28 -0
  175. package/src/util/ai-qa-policy.ts +925 -0
  176. package/src/util/ai-run-evidence-adapters.ts +4642 -0
  177. package/src/util/ai-run-evidence-dashboard.ts +323 -0
  178. package/src/util/ai-run-evidence-eval.ts +1057 -0
  179. package/src/util/ai-run-evidence.ts +1185 -0
  180. package/src/util/ai-runner-artifacts.ts +586 -0
  181. package/src/util/ai-runner-manager-autopilot.ts +961 -0
  182. package/src/util/ai-runner-manager-policy.ts +4806 -0
  183. package/src/util/ai-runner-qa-auth.ts +821 -0
  184. package/src/util/ai-runner-qa-tools.ts +3045 -0
  185. package/src/util/aicoder-runner-v6.ts +2979 -0
  186. package/src/util/common.ts +649 -0
  187. package/src/util/customer-portal-password.ts +183 -0
  188. package/src/util/error-reporter.ts +332 -0
  189. package/src/util/error-tracking.ts +79 -0
  190. package/src/util/openai-usage-cost.ts +114 -0
  191. package/src/util/report-builder-unwinds.ts +180 -0
  192. package/src/util/runner-process-janitor.ts +219 -0
  193. package/src/util/schema-report-builder.ts +448 -0
  194. package/src/util/slow-query-reporter.ts +216 -0
  195. package/src/util/subscription-dependency-context.ts +1096 -0
  196. package/src/util/support-runner-v5.ts +6573 -0
  197. package/src/util/tokenizer.ts +38 -0
  198. package/src/workers/codex-runner.worker.ts +142 -0
  199. package/start_server.sh +5 -0
  200. package/tests/ai-assistant-corpus-build.ts +484 -0
  201. package/tests/ai-assistant-corpus-replay-e2e.ts +774 -0
  202. package/tests/ai-assistant-data-parity-e2e.ts +1989 -0
  203. package/tests/ai-assistant-eval-triage.ts +831 -0
  204. package/tests/ai-assistant-openai-e2e.ts +1061 -0
  205. package/tests/ai-assistant-openai-git-e2e.ts +155 -0
  206. package/tests/ai-assistant-preflight-matrix.ts +215 -0
  207. package/tests/ai-assistant-routing-eval.test.ts +560 -0
  208. package/tests/ai-assistant-snf-live-eval.ts +975 -0
  209. package/tests/ai-assistant-utils.test.ts +3057 -0
  210. package/tests/ai-manager-autopilot-snapshot.test.ts +193 -0
  211. package/tests/ai-manager-recovery-checkpoint.test.ts +1287 -0
  212. package/tests/ai-run-eval.test.ts +132 -0
  213. package/tests/ai-run-evidence.test.ts +2129 -0
  214. package/tests/ai-runner-contract.test.ts +488 -0
  215. package/tests/aicoder-runner-v6.test.ts +751 -0
  216. package/tests/error-reporter.test.ts +145 -0
  217. package/tests/method-publication-generator.test.ts +46 -0
  218. package/tests/report-builder-linking.test.ts +79 -0
  219. package/tests/resolveio-platform-intelligence.test.ts +352 -0
  220. package/tests/server-app-cron-owner.test.ts +127 -0
  221. package/tests/subscription-connect-race.test.ts +158 -0
  222. package/tests/subscription-dependency-context.test.ts +324 -0
  223. package/tests/subscription-manager-collection-tracking.test.ts +86 -0
  224. package/tests/subscription-manager-invalidation.test.ts +86 -0
  225. package/tests/support-runner-v5.test.ts +1473 -0
  226. package/tsconfig.json +34 -0
  227. package/ai/assistant-core-heuristics.d.ts +0 -11
  228. package/ai/assistant-core-heuristics.js +0 -356
  229. package/ai/assistant-core-heuristics.js.map +0 -1
  230. package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +0 -3
  231. package/ai/resolveio-platform-intelligence-memory-corpus.js +0 -214
  232. package/ai/resolveio-platform-intelligence-memory-corpus.js.map +0 -1
  233. package/ai/resolveio-platform-intelligence-memory.d.ts +0 -20
  234. package/ai/resolveio-platform-intelligence-memory.js +0 -341
  235. package/ai/resolveio-platform-intelligence-memory.js.map +0 -1
  236. package/ai/resolveio-platform-intelligence-types.js +0 -4
  237. package/ai/resolveio-platform-intelligence-types.js.map +0 -1
  238. package/ai/resolveio-platform-intelligence.d.ts +0 -6
  239. package/ai/resolveio-platform-intelligence.js +0 -463
  240. package/ai/resolveio-platform-intelligence.js.map +0 -1
  241. package/client-server-app.d.ts +0 -1
  242. package/client-server-app.js +0 -68
  243. package/client-server-app.js.map +0 -1
  244. package/collections/ai-run.collection.d.ts +0 -3
  245. package/collections/ai-run.collection.js +0 -170
  246. package/collections/ai-run.collection.js.map +0 -1
  247. package/collections/ai-terminal-conversation.collection.d.ts +0 -2
  248. package/collections/ai-terminal-conversation.collection.js +0 -140
  249. package/collections/ai-terminal-conversation.collection.js.map +0 -1
  250. package/collections/ai-terminal-issue-report.collection.d.ts +0 -2
  251. package/collections/ai-terminal-issue-report.collection.js +0 -148
  252. package/collections/ai-terminal-issue-report.collection.js.map +0 -1
  253. package/collections/ai-terminal-message.collection.d.ts +0 -2
  254. package/collections/ai-terminal-message.collection.js +0 -121
  255. package/collections/ai-terminal-message.collection.js.map +0 -1
  256. package/collections/app-setting.collection.d.ts +0 -3
  257. package/collections/app-setting.collection.js +0 -103
  258. package/collections/app-setting.collection.js.map +0 -1
  259. package/collections/app-status.collection.d.ts +0 -3
  260. package/collections/app-status.collection.js +0 -57
  261. package/collections/app-status.collection.js.map +0 -1
  262. package/collections/communication-metric.collection.d.ts +0 -2
  263. package/collections/communication-metric.collection.js +0 -133
  264. package/collections/communication-metric.collection.js.map +0 -1
  265. package/collections/counter.collection.d.ts +0 -3
  266. package/collections/counter.collection.js +0 -56
  267. package/collections/counter.collection.js.map +0 -1
  268. package/collections/cron-job-history.collection.d.ts +0 -3
  269. package/collections/cron-job-history.collection.js +0 -137
  270. package/collections/cron-job-history.collection.js.map +0 -1
  271. package/collections/cron-job.collection.d.ts +0 -3
  272. package/collections/cron-job.collection.js +0 -92
  273. package/collections/cron-job.collection.js.map +0 -1
  274. package/collections/customer-notification.collection.d.ts +0 -3
  275. package/collections/customer-notification.collection.js +0 -130
  276. package/collections/customer-notification.collection.js.map +0 -1
  277. package/collections/customer-portal-password.collection.d.ts +0 -3
  278. package/collections/customer-portal-password.collection.js +0 -75
  279. package/collections/customer-portal-password.collection.js.map +0 -1
  280. package/collections/email-history.collection.d.ts +0 -3
  281. package/collections/email-history.collection.js +0 -134
  282. package/collections/email-history.collection.js.map +0 -1
  283. package/collections/email-verified.collection.d.ts +0 -3
  284. package/collections/email-verified.collection.js +0 -62
  285. package/collections/email-verified.collection.js.map +0 -1
  286. package/collections/file.collection.d.ts +0 -3
  287. package/collections/file.collection.js +0 -74
  288. package/collections/file.collection.js.map +0 -1
  289. package/collections/flag-update.collection.d.ts +0 -3
  290. package/collections/flag-update.collection.js +0 -57
  291. package/collections/flag-update.collection.js.map +0 -1
  292. package/collections/flag.collection.d.ts +0 -3
  293. package/collections/flag.collection.js +0 -57
  294. package/collections/flag.collection.js.map +0 -1
  295. package/collections/log-method-latency.collection.d.ts +0 -3
  296. package/collections/log-method-latency.collection.js +0 -77
  297. package/collections/log-method-latency.collection.js.map +0 -1
  298. package/collections/log-subscription.collection.d.ts +0 -3
  299. package/collections/log-subscription.collection.js +0 -80
  300. package/collections/log-subscription.collection.js.map +0 -1
  301. package/collections/log.collection.d.ts +0 -3
  302. package/collections/log.collection.js +0 -93
  303. package/collections/log.collection.js.map +0 -1
  304. package/collections/logged-in-users.collection.d.ts +0 -3
  305. package/collections/logged-in-users.collection.js +0 -67
  306. package/collections/logged-in-users.collection.js.map +0 -1
  307. package/collections/monitor-cpu.collection.d.ts +0 -3
  308. package/collections/monitor-cpu.collection.js +0 -65
  309. package/collections/monitor-cpu.collection.js.map +0 -1
  310. package/collections/monitor-function.collection.d.ts +0 -3
  311. package/collections/monitor-function.collection.js +0 -74
  312. package/collections/monitor-function.collection.js.map +0 -1
  313. package/collections/monitor-memory.collection.d.ts +0 -3
  314. package/collections/monitor-memory.collection.js +0 -77
  315. package/collections/monitor-memory.collection.js.map +0 -1
  316. package/collections/monitor-mongo.collection.d.ts +0 -3
  317. package/collections/monitor-mongo.collection.js +0 -71
  318. package/collections/monitor-mongo.collection.js.map +0 -1
  319. package/collections/notification.collection.d.ts +0 -3
  320. package/collections/notification.collection.js +0 -57
  321. package/collections/notification.collection.js.map +0 -1
  322. package/collections/openai-usage-ledger.collection.d.ts +0 -2
  323. package/collections/openai-usage-ledger.collection.js +0 -188
  324. package/collections/openai-usage-ledger.collection.js.map +0 -1
  325. package/collections/report-builder-dashboard-builder.collection.d.ts +0 -3
  326. package/collections/report-builder-dashboard-builder.collection.js +0 -109
  327. package/collections/report-builder-dashboard-builder.collection.js.map +0 -1
  328. package/collections/report-builder-library.collection.d.ts +0 -3
  329. package/collections/report-builder-library.collection.js +0 -87
  330. package/collections/report-builder-library.collection.js.map +0 -1
  331. package/collections/report-builder-report.collection.d.ts +0 -4
  332. package/collections/report-builder-report.collection.js +0 -184
  333. package/collections/report-builder-report.collection.js.map +0 -1
  334. package/collections/user-group.collection.d.ts +0 -4
  335. package/collections/user-group.collection.js +0 -89
  336. package/collections/user-group.collection.js.map +0 -1
  337. package/collections/user-guide.collection.d.ts +0 -3
  338. package/collections/user-guide.collection.js +0 -57
  339. package/collections/user-guide.collection.js.map +0 -1
  340. package/collections/user.collection.d.ts +0 -4
  341. package/collections/user.collection.js +0 -180
  342. package/collections/user.collection.js.map +0 -1
  343. package/cron/cron.d.ts +0 -14
  344. package/cron/cron.js +0 -216
  345. package/cron/cron.js.map +0 -1
  346. package/fixtures/cron-jobs.d.ts +0 -1
  347. package/fixtures/cron-jobs.js +0 -150
  348. package/fixtures/cron-jobs.js.map +0 -1
  349. package/fixtures/init.d.ts +0 -1
  350. package/fixtures/init.js +0 -91
  351. package/fixtures/init.js.map +0 -1
  352. package/http/auth.d.ts +0 -2
  353. package/http/auth.js +0 -951
  354. package/http/auth.js.map +0 -1
  355. package/http/health.d.ts +0 -1
  356. package/http/health.js +0 -11
  357. package/http/health.js.map +0 -1
  358. package/http/home.d.ts +0 -1
  359. package/http/home.js +0 -134
  360. package/http/home.js.map +0 -1
  361. package/http/slow-query-publication.d.ts +0 -2
  362. package/http/slow-query-publication.js +0 -99
  363. package/http/slow-query-publication.js.map +0 -1
  364. package/index.d.ts +0 -1
  365. package/index.js +0 -19
  366. package/index.js.map +0 -1
  367. package/managers/ai-assistant-codex-manager.manager.d.ts +0 -67
  368. package/managers/ai-assistant-codex-manager.manager.js +0 -1113
  369. package/managers/ai-assistant-codex-manager.manager.js.map +0 -1
  370. package/managers/ai-run-evidence.manager.d.ts +0 -36
  371. package/managers/ai-run-evidence.manager.js +0 -377
  372. package/managers/ai-run-evidence.manager.js.map +0 -1
  373. package/managers/communication-metric.manager.d.ts +0 -16
  374. package/managers/communication-metric.manager.js +0 -134
  375. package/managers/communication-metric.manager.js.map +0 -1
  376. package/managers/cron.manager.d.ts +0 -20
  377. package/managers/cron.manager.js +0 -534
  378. package/managers/cron.manager.js.map +0 -1
  379. package/managers/customer-notification-content.manager.d.ts +0 -55
  380. package/managers/customer-notification-content.manager.js +0 -158
  381. package/managers/customer-notification-content.manager.js.map +0 -1
  382. package/managers/diagnostic-manager-bootstrap.d.ts +0 -9
  383. package/managers/diagnostic-manager-bootstrap.js +0 -260
  384. package/managers/diagnostic-manager-bootstrap.js.map +0 -1
  385. package/managers/error-auto-fix.manager.d.ts +0 -149
  386. package/managers/error-auto-fix.manager.js +0 -3064
  387. package/managers/error-auto-fix.manager.js.map +0 -1
  388. package/managers/local-log.manager.d.ts +0 -18
  389. package/managers/local-log.manager.js +0 -88
  390. package/managers/local-log.manager.js.map +0 -1
  391. package/managers/method.manager.d.ts +0 -84
  392. package/managers/method.manager.js +0 -1964
  393. package/managers/method.manager.js.map +0 -1
  394. package/managers/mongo.manager.d.ts +0 -224
  395. package/managers/mongo.manager.js +0 -5000
  396. package/managers/mongo.manager.js.map +0 -1
  397. package/managers/monitor.manager.d.ts +0 -70
  398. package/managers/monitor.manager.js +0 -550
  399. package/managers/monitor.manager.js.map +0 -1
  400. package/managers/openai-usage-ledger.manager.d.ts +0 -30
  401. package/managers/openai-usage-ledger.manager.js +0 -142
  402. package/managers/openai-usage-ledger.manager.js.map +0 -1
  403. package/managers/slow-query-verifier.manager.d.ts +0 -144
  404. package/managers/slow-query-verifier.manager.js +0 -3857
  405. package/managers/slow-query-verifier.manager.js.map +0 -1
  406. package/managers/slow-query.manager.d.ts +0 -28
  407. package/managers/slow-query.manager.js +0 -468
  408. package/managers/slow-query.manager.js.map +0 -1
  409. package/managers/subscription.manager.d.ts +0 -169
  410. package/managers/subscription.manager.js +0 -3434
  411. package/managers/subscription.manager.js.map +0 -1
  412. package/managers/websocket.manager.d.ts +0 -73
  413. package/managers/websocket.manager.js +0 -673
  414. package/managers/websocket.manager.js.map +0 -1
  415. package/managers/worker-dispatcher.manager.d.ts +0 -120
  416. package/managers/worker-dispatcher.manager.js +0 -1266
  417. package/managers/worker-dispatcher.manager.js.map +0 -1
  418. package/managers/worker-server.manager.d.ts +0 -35
  419. package/managers/worker-server.manager.js +0 -582
  420. package/managers/worker-server.manager.js.map +0 -1
  421. package/methods/accounts.d.ts +0 -2
  422. package/methods/accounts.js +0 -624
  423. package/methods/accounts.js.map +0 -1
  424. package/methods/ai-terminal.d.ts +0 -338
  425. package/methods/ai-terminal.js +0 -23454
  426. package/methods/ai-terminal.js.map +0 -1
  427. package/methods/app-settings.d.ts +0 -2
  428. package/methods/app-settings.js +0 -169
  429. package/methods/app-settings.js.map +0 -1
  430. package/methods/aws.d.ts +0 -2
  431. package/methods/aws.js +0 -877
  432. package/methods/aws.js.map +0 -1
  433. package/methods/collections.d.ts +0 -2
  434. package/methods/collections.js +0 -719
  435. package/methods/collections.js.map +0 -1
  436. package/methods/counters.d.ts +0 -2
  437. package/methods/counters.js +0 -113
  438. package/methods/counters.js.map +0 -1
  439. package/methods/cron-jobs.d.ts +0 -2
  440. package/methods/cron-jobs.js +0 -2475
  441. package/methods/cron-jobs.js.map +0 -1
  442. package/methods/customer-notifications.d.ts +0 -2
  443. package/methods/customer-notifications.js +0 -528
  444. package/methods/customer-notifications.js.map +0 -1
  445. package/methods/diagnostics.d.ts +0 -2
  446. package/methods/diagnostics.js +0 -703
  447. package/methods/diagnostics.js.map +0 -1
  448. package/methods/flag-updates.d.ts +0 -2
  449. package/methods/flag-updates.js +0 -8
  450. package/methods/flag-updates.js.map +0 -1
  451. package/methods/flags.d.ts +0 -2
  452. package/methods/flags.js +0 -8
  453. package/methods/flags.js.map +0 -1
  454. package/methods/logs.d.ts +0 -2
  455. package/methods/logs.js +0 -751
  456. package/methods/logs.js.map +0 -1
  457. package/methods/mongo-explorer.d.ts +0 -2
  458. package/methods/mongo-explorer.js +0 -1808
  459. package/methods/mongo-explorer.js.map +0 -1
  460. package/methods/monitor.d.ts +0 -2
  461. package/methods/monitor.js +0 -543
  462. package/methods/monitor.js.map +0 -1
  463. package/methods/pdf.d.ts +0 -2
  464. package/methods/pdf.js +0 -1216
  465. package/methods/pdf.js.map +0 -1
  466. package/methods/publications.d.ts +0 -1
  467. package/methods/publications.js +0 -183
  468. package/methods/publications.js.map +0 -1
  469. package/methods/report-builder.d.ts +0 -2
  470. package/methods/report-builder.js +0 -3094
  471. package/methods/report-builder.js.map +0 -1
  472. package/methods/support.d.ts +0 -2
  473. package/methods/support.js +0 -430
  474. package/methods/support.js.map +0 -1
  475. package/models/ai-run.model.d.ts +0 -19
  476. package/models/ai-run.model.js +0 -4
  477. package/models/ai-run.model.js.map +0 -1
  478. package/models/ai-terminal-conversation.model.d.ts +0 -17
  479. package/models/ai-terminal-conversation.model.js +0 -4
  480. package/models/ai-terminal-conversation.model.js.map +0 -1
  481. package/models/ai-terminal-issue-report.model.d.ts +0 -19
  482. package/models/ai-terminal-issue-report.model.js +0 -4
  483. package/models/ai-terminal-issue-report.model.js.map +0 -1
  484. package/models/ai-terminal-message.model.d.ts +0 -22
  485. package/models/ai-terminal-message.model.js +0 -4
  486. package/models/ai-terminal-message.model.js.map +0 -1
  487. package/models/app-setting.model.d.ts +0 -16
  488. package/models/app-setting.model.js +0 -4
  489. package/models/app-setting.model.js.map +0 -1
  490. package/models/app-status.model.js +0 -4
  491. package/models/app-status.model.js.map +0 -1
  492. package/models/billing-logged-in-users.model.js +0 -4
  493. package/models/billing-logged-in-users.model.js.map +0 -1
  494. package/models/collection-document.model.d.ts +0 -21
  495. package/models/collection-document.model.js +0 -4
  496. package/models/collection-document.model.js.map +0 -1
  497. package/models/communication-metric.model.d.ts +0 -20
  498. package/models/communication-metric.model.js +0 -4
  499. package/models/communication-metric.model.js.map +0 -1
  500. package/models/counter.model.js +0 -4
  501. package/models/counter.model.js.map +0 -1
  502. package/models/cron-job-history.model.d.ts +0 -15
  503. package/models/cron-job-history.model.js +0 -4
  504. package/models/cron-job-history.model.js.map +0 -1
  505. package/models/cron-job.model.d.ts +0 -14
  506. package/models/cron-job.model.js +0 -4
  507. package/models/cron-job.model.js.map +0 -1
  508. package/models/customer-notification.model.d.ts +0 -26
  509. package/models/customer-notification.model.js +0 -4
  510. package/models/customer-notification.model.js.map +0 -1
  511. package/models/customer-portal-password.model.d.ts +0 -11
  512. package/models/customer-portal-password.model.js +0 -4
  513. package/models/customer-portal-password.model.js.map +0 -1
  514. package/models/dialog.model.d.ts +0 -23
  515. package/models/dialog.model.js +0 -4
  516. package/models/dialog.model.js.map +0 -1
  517. package/models/email-history.model.d.ts +0 -32
  518. package/models/email-history.model.js.map +0 -1
  519. package/models/email-verified.model.js +0 -4
  520. package/models/email-verified.model.js.map +0 -1
  521. package/models/file.model.js +0 -4
  522. package/models/file.model.js.map +0 -1
  523. package/models/flag-update.model.js +0 -4
  524. package/models/flag-update.model.js.map +0 -1
  525. package/models/flag.model.js +0 -4
  526. package/models/flag.model.js.map +0 -1
  527. package/models/log-method-latency.model.d.ts +0 -10
  528. package/models/log-method-latency.model.js +0 -4
  529. package/models/log-method-latency.model.js.map +0 -1
  530. package/models/log-subscription.model.js +0 -4
  531. package/models/log-subscription.model.js.map +0 -1
  532. package/models/log.model.d.ts +0 -17
  533. package/models/log.model.js +0 -4
  534. package/models/log.model.js.map +0 -1
  535. package/models/logged-in-users.model.js +0 -4
  536. package/models/logged-in-users.model.js.map +0 -1
  537. package/models/method-response.model.js +0 -4
  538. package/models/method-response.model.js.map +0 -1
  539. package/models/method.model.d.ts +0 -26
  540. package/models/method.model.js +0 -4
  541. package/models/method.model.js.map +0 -1
  542. package/models/monitor-cpu.model.js +0 -4
  543. package/models/monitor-cpu.model.js.map +0 -1
  544. package/models/monitor-function.model.d.ts +0 -14
  545. package/models/monitor-function.model.js +0 -4
  546. package/models/monitor-function.model.js.map +0 -1
  547. package/models/monitor-memory.model.d.ts +0 -15
  548. package/models/monitor-memory.model.js +0 -4
  549. package/models/monitor-memory.model.js.map +0 -1
  550. package/models/monitor-mongo.model.d.ts +0 -13
  551. package/models/monitor-mongo.model.js +0 -4
  552. package/models/monitor-mongo.model.js.map +0 -1
  553. package/models/notification.model.js +0 -4
  554. package/models/notification.model.js.map +0 -1
  555. package/models/openai-usage-ledger.model.d.ts +0 -30
  556. package/models/openai-usage-ledger.model.js +0 -4
  557. package/models/openai-usage-ledger.model.js.map +0 -1
  558. package/models/pagination.model.d.ts +0 -11
  559. package/models/pagination.model.js +0 -28
  560. package/models/pagination.model.js.map +0 -1
  561. package/models/permission.model.d.ts +0 -12
  562. package/models/permission.model.js +0 -4
  563. package/models/permission.model.js.map +0 -1
  564. package/models/report-builder-dashboard-builder.model.d.ts +0 -25
  565. package/models/report-builder-dashboard-builder.model.js +0 -4
  566. package/models/report-builder-dashboard-builder.model.js.map +0 -1
  567. package/models/report-builder-library.model.d.ts +0 -17
  568. package/models/report-builder-library.model.js +0 -4
  569. package/models/report-builder-library.model.js.map +0 -1
  570. package/models/report-builder-report.model.d.ts +0 -121
  571. package/models/report-builder-report.model.js +0 -4
  572. package/models/report-builder-report.model.js.map +0 -1
  573. package/models/report-builder.model.d.ts +0 -61
  574. package/models/report-builder.model.js +0 -4
  575. package/models/report-builder.model.js.map +0 -1
  576. package/models/select-data-label.model.d.ts +0 -9
  577. package/models/select-data-label.model.js +0 -4
  578. package/models/select-data-label.model.js.map +0 -1
  579. package/models/server-message.model.d.ts +0 -32
  580. package/models/server-message.model.js +0 -4
  581. package/models/server-message.model.js.map +0 -1
  582. package/models/slow-query-report.model.d.ts +0 -23
  583. package/models/slow-query-report.model.js +0 -4
  584. package/models/slow-query-report.model.js.map +0 -1
  585. package/models/subscription.model.d.ts +0 -31
  586. package/models/subscription.model.js +0 -4
  587. package/models/subscription.model.js.map +0 -1
  588. package/models/support-ticket.model.d.ts +0 -87
  589. package/models/support-ticket.model.js +0 -4
  590. package/models/support-ticket.model.js.map +0 -1
  591. package/models/user-group.model.d.ts +0 -20
  592. package/models/user-group.model.js +0 -4
  593. package/models/user-group.model.js.map +0 -1
  594. package/models/user-guide.model.js +0 -4
  595. package/models/user-guide.model.js.map +0 -1
  596. package/models/user.model.d.ts +0 -84
  597. package/models/user.model.js +0 -4
  598. package/models/user.model.js.map +0 -1
  599. package/private/images/ResolveIO.png +0 -0
  600. package/public_api.js +0 -127
  601. package/public_api.js.map +0 -1
  602. package/publications/ai-terminal.d.ts +0 -1
  603. package/publications/ai-terminal.js +0 -122
  604. package/publications/ai-terminal.js.map +0 -1
  605. package/publications/app-settings.d.ts +0 -2
  606. package/publications/app-settings.js +0 -28
  607. package/publications/app-settings.js.map +0 -1
  608. package/publications/app-status.d.ts +0 -2
  609. package/publications/app-status.js +0 -16
  610. package/publications/app-status.js.map +0 -1
  611. package/publications/cron-jobs.d.ts +0 -2
  612. package/publications/cron-jobs.js +0 -88
  613. package/publications/cron-jobs.js.map +0 -1
  614. package/publications/customer-notifications.d.ts +0 -2
  615. package/publications/customer-notifications.js +0 -161
  616. package/publications/customer-notifications.js.map +0 -1
  617. package/publications/files.d.ts +0 -2
  618. package/publications/files.js +0 -36
  619. package/publications/files.js.map +0 -1
  620. package/publications/flags-update.d.ts +0 -2
  621. package/publications/flags-update.js +0 -22
  622. package/publications/flags-update.js.map +0 -1
  623. package/publications/flags.d.ts +0 -2
  624. package/publications/flags.js +0 -22
  625. package/publications/flags.js.map +0 -1
  626. package/publications/logs.d.ts +0 -2
  627. package/publications/logs.js +0 -164
  628. package/publications/logs.js.map +0 -1
  629. package/publications/notifications.d.ts +0 -2
  630. package/publications/notifications.js +0 -16
  631. package/publications/notifications.js.map +0 -1
  632. package/publications/report-builder-dashboard-builders.d.ts +0 -2
  633. package/publications/report-builder-dashboard-builders.js +0 -42
  634. package/publications/report-builder-dashboard-builders.js.map +0 -1
  635. package/publications/report-builder-libraries.d.ts +0 -2
  636. package/publications/report-builder-libraries.js +0 -90
  637. package/publications/report-builder-libraries.js.map +0 -1
  638. package/publications/report-builder-reports.d.ts +0 -2
  639. package/publications/report-builder-reports.js +0 -50
  640. package/publications/report-builder-reports.js.map +0 -1
  641. package/publications/super-admin.d.ts +0 -2
  642. package/publications/super-admin.js +0 -16
  643. package/publications/super-admin.js.map +0 -1
  644. package/publications/user-groups.d.ts +0 -1
  645. package/publications/user-groups.js +0 -16
  646. package/publications/user-groups.js.map +0 -1
  647. package/publications/user-guides.d.ts +0 -1
  648. package/publications/user-guides.js +0 -16
  649. package/publications/user-guides.js.map +0 -1
  650. package/resolveio-server-app.d.ts +0 -70
  651. package/resolveio-server-app.js +0 -801
  652. package/resolveio-server-app.js.map +0 -1
  653. package/server-app.d.ts +0 -228
  654. package/server-app.js +0 -3566
  655. package/server-app.js.map +0 -1
  656. package/services/codex-client.d.ts +0 -128
  657. package/services/codex-client.js +0 -1629
  658. package/services/codex-client.js.map +0 -1
  659. package/services/openai-client.d.ts +0 -46
  660. package/services/openai-client.js +0 -318
  661. package/services/openai-client.js.map +0 -1
  662. package/types/error-report.d.ts +0 -25
  663. package/types/error-report.js +0 -4
  664. package/types/error-report.js.map +0 -1
  665. package/types/slow-query-report.d.ts +0 -27
  666. package/types/slow-query-report.js +0 -6
  667. package/types/slow-query-report.js.map +0 -1
  668. package/util/ai-qa-policy.d.ts +0 -124
  669. package/util/ai-qa-policy.js +0 -736
  670. package/util/ai-qa-policy.js.map +0 -1
  671. package/util/ai-run-evidence-adapters.d.ts +0 -109
  672. package/util/ai-run-evidence-adapters.js +0 -4124
  673. package/util/ai-run-evidence-adapters.js.map +0 -1
  674. package/util/ai-run-evidence-dashboard.d.ts +0 -84
  675. package/util/ai-run-evidence-dashboard.js +0 -336
  676. package/util/ai-run-evidence-dashboard.js.map +0 -1
  677. package/util/ai-run-evidence-eval.d.ts +0 -86
  678. package/util/ai-run-evidence-eval.js +0 -1018
  679. package/util/ai-run-evidence-eval.js.map +0 -1
  680. package/util/ai-run-evidence.d.ts +0 -244
  681. package/util/ai-run-evidence.js +0 -852
  682. package/util/ai-run-evidence.js.map +0 -1
  683. package/util/ai-runner-artifacts.d.ts +0 -82
  684. package/util/ai-runner-artifacts.js +0 -713
  685. package/util/ai-runner-artifacts.js.map +0 -1
  686. package/util/ai-runner-manager-autopilot.d.ts +0 -210
  687. package/util/ai-runner-manager-autopilot.js +0 -642
  688. package/util/ai-runner-manager-autopilot.js.map +0 -1
  689. package/util/ai-runner-manager-policy.d.ts +0 -767
  690. package/util/ai-runner-manager-policy.js +0 -3304
  691. package/util/ai-runner-manager-policy.js.map +0 -1
  692. package/util/ai-runner-qa-auth.d.ts +0 -5
  693. package/util/ai-runner-qa-auth.js +0 -822
  694. package/util/ai-runner-qa-auth.js.map +0 -1
  695. package/util/ai-runner-qa-tools.d.ts +0 -26
  696. package/util/ai-runner-qa-tools.js +0 -3029
  697. package/util/ai-runner-qa-tools.js.map +0 -1
  698. package/util/aicoder-runner-v6.d.ts +0 -423
  699. package/util/aicoder-runner-v6.js +0 -2177
  700. package/util/aicoder-runner-v6.js.map +0 -1
  701. package/util/common.d.ts +0 -31
  702. package/util/common.js +0 -683
  703. package/util/common.js.map +0 -1
  704. package/util/customer-portal-password.d.ts +0 -13
  705. package/util/customer-portal-password.js +0 -209
  706. package/util/customer-portal-password.js.map +0 -1
  707. package/util/error-reporter.d.ts +0 -52
  708. package/util/error-reporter.js +0 -326
  709. package/util/error-reporter.js.map +0 -1
  710. package/util/error-tracking.d.ts +0 -13
  711. package/util/error-tracking.js +0 -120
  712. package/util/error-tracking.js.map +0 -1
  713. package/util/openai-usage-cost.d.ts +0 -6
  714. package/util/openai-usage-cost.js +0 -103
  715. package/util/openai-usage-cost.js.map +0 -1
  716. package/util/report-builder-unwinds.d.ts +0 -15
  717. package/util/report-builder-unwinds.js +0 -156
  718. package/util/report-builder-unwinds.js.map +0 -1
  719. package/util/runner-process-janitor.d.ts +0 -27
  720. package/util/runner-process-janitor.js +0 -208
  721. package/util/runner-process-janitor.js.map +0 -1
  722. package/util/schema-report-builder.d.ts +0 -6
  723. package/util/schema-report-builder.js +0 -481
  724. package/util/schema-report-builder.js.map +0 -1
  725. package/util/slow-query-reporter.d.ts +0 -28
  726. package/util/slow-query-reporter.js +0 -226
  727. package/util/slow-query-reporter.js.map +0 -1
  728. package/util/subscription-dependency-context.d.ts +0 -34
  729. package/util/subscription-dependency-context.js +0 -1283
  730. package/util/subscription-dependency-context.js.map +0 -1
  731. package/util/support-runner-v5.d.ts +0 -1018
  732. package/util/support-runner-v5.js +0 -4615
  733. package/util/support-runner-v5.js.map +0 -1
  734. package/util/tokenizer.d.ts +0 -5
  735. package/util/tokenizer.js +0 -41
  736. package/util/tokenizer.js.map +0 -1
  737. package/workers/codex-runner.worker.d.ts +0 -1
  738. package/workers/codex-runner.worker.js +0 -192
  739. package/workers/codex-runner.worker.js.map +0 -1
  740. /package/{private → src/private}/email-templates/enrollment.html +0 -0
  741. /package/{private → src/private}/email-templates/forgot-password.html +0 -0
  742. /package/{private → src/private}/email-templates/support-ticket-deleted.html +0 -0
  743. /package/{private → src/private}/email-templates/support-ticket-modified.html +0 -0
  744. /package/{private → src/private}/email-templates/support-ticket.html +0 -0
  745. /package/{public_api.d.ts → src/public_api.ts} +0 -0
@@ -0,0 +1,4642 @@
1
+ import {
2
+ AIRun,
3
+ AIRunCost,
4
+ AIRunEvent,
5
+ AIRunGateResult,
6
+ AIQaArtifact,
7
+ AIQaBusinessAssertion,
8
+ AIQaCompileResult,
9
+ AIQaInfraCheck,
10
+ AIQaRun,
11
+ AIQaRouteProbe,
12
+ buildAIRun,
13
+ buildAIRunCost,
14
+ buildAIQaRun,
15
+ normalizeOpenAIUsageRowsForCosting
16
+ } from './ai-run-evidence';
17
+ import {
18
+ evaluateResolveIOAICoderWorkflowProofReadiness,
19
+ ResolveIOAICoderJourneyContractValidationResult,
20
+ ResolveIOAICoderWorkflowProofReadiness,
21
+ validateResolveIOAICoderJourneyContract
22
+ } from './aicoder-runner-v6';
23
+ import {
24
+ buildResolveIOAIManagerHotfixDurabilityContract,
25
+ decideResolveIOAIManagerPolicy,
26
+ ResolveIOAIManagerFailureRecord,
27
+ validateResolveIOAIManagerRecoveryExecutionProofContract,
28
+ validateResolveIOAIManagerHotfixEvidence
29
+ } from './ai-runner-manager-policy';
30
+ import {
31
+ buildResolveIOSupportDiagnosisEvidencePack,
32
+ buildResolveIOSupportIssueClassProbePlan,
33
+ changedFilesOutsideResolveIOSupportDiagnosisOwnerFiles,
34
+ validateResolveIOSupportClarificationContract,
35
+ validateResolveIOSupportCustomerReplyReadinessContract,
36
+ validateResolveIOSupportIssueClassProbePlan,
37
+ validateResolveIOSupportDiagnosisGate
38
+ } from './support-runner-v5';
39
+
40
+ export interface SupportAIRunAdapterInput {
41
+ ticket?: Record<string, any>;
42
+ job?: Record<string, any>;
43
+ versions?: Array<Record<string, any>>;
44
+ buildPlans?: Array<Record<string, any>>;
45
+ aiJobs?: Array<Record<string, any>>;
46
+ taskEvents?: Array<Record<string, any>>;
47
+ usageLedger?: Array<Record<string, any>>;
48
+ commits?: Array<Record<string, any>>;
49
+ qaEvidence?: Record<string, any>;
50
+ now?: Date | string;
51
+ }
52
+
53
+ export interface AICoderAIRunAdapterInput {
54
+ app?: Record<string, any>;
55
+ job?: Record<string, any>;
56
+ logs?: Array<Record<string, any>>;
57
+ usageLedger?: Array<Record<string, any>>;
58
+ commits?: Array<Record<string, any>>;
59
+ qaEvidence?: Record<string, any>;
60
+ now?: Date | string;
61
+ }
62
+
63
+ export interface AssistantAIRunAdapterInput {
64
+ runId?: string;
65
+ requestId?: string;
66
+ messageId?: string;
67
+ conversation?: Record<string, any>;
68
+ messages?: Array<Record<string, any>>;
69
+ issueReports?: Array<Record<string, any>>;
70
+ usageLedger?: Array<Record<string, any>>;
71
+ correctnessChecks?: Array<Record<string, any>>;
72
+ answerQuality?: Record<string, any>;
73
+ now?: Date | string;
74
+ }
75
+
76
+ export type AssistantAnswerQualityStatus =
77
+ | 'ready'
78
+ | 'missing_data_source'
79
+ | 'date_incorrect'
80
+ | 'missing_date_window'
81
+ | 'illegal_query_shape'
82
+ | 'missing_query_proof'
83
+ | 'query_error'
84
+ | 'permission_error'
85
+ | 'no_data_unverified'
86
+ | 'missing_citations'
87
+ | 'low_confidence'
88
+ | 'missing_next_action'
89
+ | 'incorrect'
90
+ | 'blocked';
91
+
92
+ export interface AssistantAnswerQualityInput {
93
+ answerQuality?: Record<string, any>;
94
+ correctnessChecks?: Array<Record<string, any>>;
95
+ now?: Date | string;
96
+ }
97
+
98
+ export interface AssistantAnswerQualityDecision {
99
+ ready: boolean;
100
+ status: AssistantAnswerQualityStatus;
101
+ reason: string;
102
+ blockers: string[];
103
+ queryStatus: 'ok' | 'no_data' | 'query_error' | 'permission_error' | 'unknown';
104
+ confidenceLevel: 'high' | 'medium' | 'low' | 'unknown';
105
+ dateBasis?: string;
106
+ expectedCurrentDate?: string;
107
+ dateWindowRequired: boolean;
108
+ dateWindowPresent: boolean;
109
+ dateWindow?: {
110
+ startDate?: string;
111
+ endDate?: string;
112
+ mode?: string;
113
+ months?: number;
114
+ };
115
+ noDataConfirmed: boolean;
116
+ legalQueryShape: boolean;
117
+ citationRefs: string[];
118
+ queryEvidenceRequired: boolean;
119
+ queryEvidencePresent: boolean;
120
+ queryEvidenceRefs: string[];
121
+ queryExecutionCount: number;
122
+ nextActions: string[];
123
+ evidenceRefs: string[];
124
+ failedChecks: string[];
125
+ recordedAt: string;
126
+ }
127
+
128
+ export type AssistantAnswerActionabilityStatus =
129
+ | 'ready_to_answer'
130
+ | 'needs_query_repair'
131
+ | 'needs_permission'
132
+ | 'needs_no_data_verification'
133
+ | 'needs_date_window_repair'
134
+ | 'needs_query_shape_repair'
135
+ | 'needs_evidence'
136
+ | 'needs_confidence_review'
137
+ | 'needs_next_action'
138
+ | 'incorrect'
139
+ | 'blocked';
140
+
141
+ export interface AssistantAnswerActionabilityContract {
142
+ contractId: string;
143
+ status: AssistantAnswerActionabilityStatus;
144
+ primaryCommand: string;
145
+ label: string;
146
+ canAnswerCustomer: boolean;
147
+ canDraftSupportReply: boolean;
148
+ canSendCustomerReply: boolean;
149
+ requiresHumanReview: boolean;
150
+ canRunWithoutCodexMonitor: boolean;
151
+ codexFallbackRequired: boolean;
152
+ costRisk: 'free_or_deterministic' | 'small_model_or_qa' | 'expensive_model' | 'release_or_customer_send' | 'manual_blocked';
153
+ queryResultClass: AssistantAnswerQualityDecision['queryStatus'];
154
+ confidenceLevel: AssistantAnswerQualityDecision['confidenceLevel'];
155
+ decisionBasis: {
156
+ answerQualityStatus: AssistantAnswerQualityStatus;
157
+ queryStatus: AssistantAnswerQualityDecision['queryStatus'];
158
+ queryEvidencePresent: boolean;
159
+ noDataConfirmed: boolean;
160
+ legalQueryShape: boolean;
161
+ dateWindowRequired: boolean;
162
+ dateWindowPresent: boolean;
163
+ citationCount: number;
164
+ nextActionCount: number;
165
+ };
166
+ requiredEvidence: string[];
167
+ successEvidence: string[];
168
+ blockers: string[];
169
+ nextActions: string[];
170
+ nextCommands: string[];
171
+ forbiddenActions: string[];
172
+ evidenceRefs: string[];
173
+ recordedAt: string;
174
+ }
175
+
176
+ function cleanText(value: any, max = 1000): string {
177
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, max);
178
+ }
179
+
180
+ function idText(value: any, max = 160): string | undefined {
181
+ const normalized = cleanText(value, max);
182
+ return normalized || undefined;
183
+ }
184
+
185
+ function asArray<T = any>(value: any): T[] {
186
+ return Array.isArray(value) ? value : [];
187
+ }
188
+
189
+ function cleanStringList(value: any, limit = 40, max = 500): string[] {
190
+ return asArray<any>(value)
191
+ .map((entry) => cleanText(entry, max))
192
+ .filter(Boolean)
193
+ .slice(0, limit);
194
+ }
195
+
196
+ function plainObject(value: any): Record<string, any> {
197
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
198
+ }
199
+
200
+ function firstText(source: Record<string, any> | undefined, fields: string[], max = 1000): string | undefined {
201
+ for (const field of fields) {
202
+ const value = source?.[field];
203
+ const normalized = idText(value, max);
204
+ if (normalized) {
205
+ return normalized;
206
+ }
207
+ }
208
+ return undefined;
209
+ }
210
+
211
+ function dateValue(source: Record<string, any> | undefined, fields: string[]): Date | string | undefined {
212
+ for (const field of fields) {
213
+ const value = source?.[field];
214
+ if (value) {
215
+ return value;
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+
221
+ function addSourceId(sourceIds: Record<string, string>, key: string, value: any): void {
222
+ const normalized = idText(value);
223
+ if (normalized) {
224
+ sourceIds[key] = normalized;
225
+ }
226
+ }
227
+
228
+ function pushEvent(events: AIRunEvent[], event: AIRunEvent): void {
229
+ const message = cleanText(event.message, 1800);
230
+ const metadata = event.metadata || {};
231
+ if (!message && !Object.keys(metadata).length && !event.artifactPaths?.length) {
232
+ return;
233
+ }
234
+ events.push({
235
+ ...event,
236
+ message,
237
+ artifactPaths: asArray<string>(event.artifactPaths).filter(Boolean).slice(0, 20)
238
+ });
239
+ }
240
+
241
+ function eventDate(record: Record<string, any>): Date | string | undefined {
242
+ return dateValue(record, ['recordedAt', 'createdAt', 'updatedAt', 'timestamp', 'time', 'date']);
243
+ }
244
+
245
+ function normalizeManagerFailureRecord(value: any, fallback: Partial<ResolveIOAIManagerFailureRecord> = {}): ResolveIOAIManagerFailureRecord | undefined {
246
+ const entry = plainObject(value);
247
+ if (!Object.keys(entry).length && !Object.keys(fallback).length) {
248
+ return undefined;
249
+ }
250
+ const blocker = cleanText(entry.blocker || entry.reason || entry.summary || entry.message || fallback.blocker || fallback.summary, 1200);
251
+ const failureClass = cleanText(entry.failureClass || entry.failure_class || entry.class || fallback.failureClass, 120);
252
+ const evidenceHash = cleanText(entry.evidenceHash || entry.evidence_hash || entry.hash || fallback.evidenceHash, 180);
253
+ const blockerFingerprint = cleanText(entry.blockerFingerprint || entry.blocker_fingerprint || fallback.blockerFingerprint, 180);
254
+ const lane = cleanText(entry.lane || fallback.lane || 'support', 80);
255
+ const stepType = cleanText(entry.stepType || entry.step_type || entry.type || fallback.stepType || 'support_step', 80);
256
+ const outcome = cleanText(entry.outcome || entry.status || entry.result || fallback.outcome || 'needs_repair', 80);
257
+ if (!blocker && !failureClass && !evidenceHash && !blockerFingerprint) {
258
+ return undefined;
259
+ }
260
+ return {
261
+ lane,
262
+ stepType,
263
+ outcome,
264
+ failureClass: failureClass || undefined,
265
+ blocker: blocker || undefined,
266
+ summary: cleanText(entry.summary || entry.message || fallback.summary, 1200) || undefined,
267
+ blockerFingerprint: blockerFingerprint || undefined,
268
+ evidenceHash: evidenceHash || undefined,
269
+ changedFiles: cleanStringList(entry.changedFiles || entry.changed_files || fallback.changedFiles, 80, 500),
270
+ artifactPaths: cleanStringList(entry.artifactPaths || entry.artifact_paths || entry.artifacts || fallback.artifactPaths, 80, 500)
271
+ };
272
+ }
273
+
274
+ function supportManagerFailureRecords(job: Record<string, any>, evidence: Record<string, any>): ResolveIOAIManagerFailureRecord[] {
275
+ const records: ResolveIOAIManagerFailureRecord[] = [];
276
+ const pushRecord = (value: any, fallback: Partial<ResolveIOAIManagerFailureRecord> = {}): void => {
277
+ const record = normalizeManagerFailureRecord(value, fallback);
278
+ if (record) {
279
+ records.push(record);
280
+ }
281
+ };
282
+ for (const entry of asArray<any>(job.supportV5StepHistory || job.support_v5_step_history || evidence.supportV5StepHistory || evidence.support_v5_step_history)) {
283
+ pushRecord(entry);
284
+ }
285
+ for (const entry of asArray<any>(job.supportV5FailureFingerprints || job.support_v5_failure_fingerprints || evidence.supportV5FailureFingerprints || evidence.support_v5_failure_fingerprints)) {
286
+ const fingerprint = plainObject(entry);
287
+ const count = Math.max(1, Math.min(8, Number(fingerprint.count || fingerprint.repeatedCount || fingerprint.repeated_count || 1) || 1));
288
+ const fallback = {
289
+ lane: cleanText(fingerprint.lane || 'support', 80),
290
+ stepType: cleanText(fingerprint.stepType || fingerprint.step_type || 'support_step', 80),
291
+ outcome: cleanText(fingerprint.outcome || 'needs_repair', 80),
292
+ failureClass: cleanText(fingerprint.failureClass || fingerprint.failure_class || fingerprint.class, 120),
293
+ blocker: cleanText(fingerprint.blocker || fingerprint.reason || fingerprint.summary, 1200),
294
+ blockerFingerprint: cleanText(fingerprint.blockerFingerprint || fingerprint.blocker_fingerprint, 180),
295
+ evidenceHash: cleanText(fingerprint.evidenceHash || fingerprint.evidence_hash, 180)
296
+ };
297
+ for (let index = 0; index < count; index += 1) {
298
+ pushRecord(fingerprint, fallback);
299
+ }
300
+ }
301
+ const currentFailure = evidenceObject(
302
+ job.supportV5CurrentFailure,
303
+ job.support_v5_current_failure,
304
+ job.supportV5RecoveryCurrent,
305
+ job.support_v5_recovery_current,
306
+ evidence.currentFailure,
307
+ evidence.current_failure
308
+ );
309
+ pushRecord(currentFailure);
310
+ return records.slice(-80);
311
+ }
312
+
313
+ function managerNoBlindLoopGateFromHistory(
314
+ history: ResolveIOAIManagerFailureRecord[],
315
+ budgetSource: Record<string, any>,
316
+ now?: Date | string
317
+ ): AIRunGateResult | undefined {
318
+ if (!history.length) {
319
+ return undefined;
320
+ }
321
+ const budget = plainObject(budgetSource);
322
+ const maxSameFailureRepeats = Math.max(1, Number(budget.maxRepeatedNoProgress || budget.max_repeated_no_progress || budget.maxSameFailureRepeats || budget.max_same_failure_repeats || 3) || 3);
323
+ const decision = decideResolveIOAIManagerPolicy({
324
+ history,
325
+ maxSameFailureRepeats
326
+ });
327
+ const parksRun = /^(park_repeated_failure|park_ping_pong|manual_handoff)$/.test(decision.action);
328
+ const status: AIRunGateResult['status'] = parksRun
329
+ ? 'blocked'
330
+ : decision.action === 'retry_infra'
331
+ ? 'warn'
332
+ : 'pass';
333
+ return {
334
+ key: 'manager_no_blind_loop_policy',
335
+ label: 'No-blind-loop manager policy',
336
+ status,
337
+ reason: decision.reason,
338
+ evidenceRefs: cleanStringList(decision.recoveryAction.requiredArtifacts, 20, 500),
339
+ recordedAt: isoNow(now),
340
+ metadata: {
341
+ action: decision.action,
342
+ failureClass: decision.failureClass,
343
+ blockerFingerprint: decision.blockerFingerprint,
344
+ evidenceHash: decision.evidenceHash,
345
+ sameFailureCount: decision.sameFailureCount,
346
+ pingPongCount: decision.pingPongCount,
347
+ newEvidence: decision.newEvidence,
348
+ materialEvidence: decision.materialEvidence,
349
+ evidenceStrength: decision.evidenceStrength,
350
+ evidenceSignals: decision.evidenceSignals,
351
+ loopBudgetShouldReset: decision.loopBudgetShouldReset,
352
+ productRepairFailure: decision.productRepairFailure,
353
+ recoveryClass: decision.recoveryPlan.recoveryClass,
354
+ allowedAction: decision.recoveryPlan.allowedAction,
355
+ dispatchAction: decision.recoveryAction.allowedDispatchAction,
356
+ evidenceOnly: decision.recoveryAction.evidenceOnly,
357
+ productRepairAllowed: decision.recoveryAction.productRepairAllowed,
358
+ expensiveModelAllowed: decision.recoveryAction.expensiveModelAllowed,
359
+ costCeilingUsd: decision.recoveryAction.costCeilingUsd,
360
+ requiresNewEvidence: decision.recoveryAction.retryPolicy?.requireNewEvidence === true,
361
+ nextCommands: decision.recoveryAction.nextCommands,
362
+ requiredEvidence: decision.recoveryAction.requiredArtifacts,
363
+ forbiddenActions: decision.recoveryPlan.forbiddenActions
364
+ }
365
+ };
366
+ }
367
+
368
+ function managerRecoveryExecutionProofGate(
369
+ proof: Record<string, any>,
370
+ now?: Date | string
371
+ ): AIRunGateResult | undefined {
372
+ if (!proof || !Object.keys(proof).length) {
373
+ return undefined;
374
+ }
375
+ const validation = validateResolveIOAIManagerRecoveryExecutionProofContract(proof);
376
+ const normalized = validation.normalized;
377
+ const statusText = cleanText(normalized.status, 120);
378
+ const gateStatus: AIRunGateResult['status'] = validation.valid && normalized.canContinueRun === true && statusText === 'proof_ready'
379
+ ? 'pass'
380
+ : statusText === 'manual_handoff'
381
+ ? 'warn'
382
+ : 'blocked';
383
+ return {
384
+ key: 'manager_recovery_execution_proof',
385
+ label: 'Manager recovery execution proof',
386
+ status: gateStatus,
387
+ reason: normalized.blockers[0] || validation.blockers[0] || statusText || 'Manager recovery execution proof recorded.',
388
+ evidenceRefs: normalized.artifactPaths,
389
+ recordedAt: isoNow(now || normalized.createdAt),
390
+ metadata: {
391
+ contractId: normalized.contractId,
392
+ directiveId: normalized.directiveId,
393
+ surface: normalized.surface,
394
+ dispatchAction: normalized.dispatchAction,
395
+ phase: normalized.phase,
396
+ status: statusText,
397
+ canContinueRun: normalized.canContinueRun,
398
+ canRunProductRepair: normalized.canRunProductRepair,
399
+ canRunExpensiveModel: normalized.canRunExpensiveModel,
400
+ canResetLoopBudget: normalized.canResetLoopBudget,
401
+ proofRequiredBeforeContinuation: normalized.proofRequiredBeforeContinuation,
402
+ requiresNewEvidence: normalized.requiresNewEvidence,
403
+ newEvidence: normalized.newEvidence,
404
+ materialEvidence: normalized.materialEvidence,
405
+ evidenceStrength: normalized.evidenceStrength,
406
+ evidenceSignals: normalized.evidenceSignals,
407
+ startingFailureClass: normalized.startingFailureClass,
408
+ startingBlockerFingerprint: normalized.startingBlockerFingerprint,
409
+ startingEvidenceHash: normalized.startingEvidenceHash,
410
+ latestFailureClass: normalized.latestFailureClass,
411
+ latestBlockerFingerprint: normalized.latestBlockerFingerprint,
412
+ latestEvidenceHash: normalized.latestEvidenceHash,
413
+ requiredEvidence: normalized.requiredEvidence,
414
+ missingEvidence: normalized.missingEvidence,
415
+ changedFiles: normalized.changedFiles,
416
+ blockers: Array.from(new Set([...normalized.blockers, ...validation.blockers])),
417
+ nextAllowedAction: normalized.nextAllowedAction,
418
+ forbiddenActions: normalized.forbiddenActions
419
+ }
420
+ };
421
+ }
422
+
423
+ function supportManagerNoBlindLoopGate(
424
+ job: Record<string, any>,
425
+ evidence: Record<string, any>,
426
+ now?: Date | string
427
+ ): AIRunGateResult | undefined {
428
+ return managerNoBlindLoopGateFromHistory(
429
+ supportManagerFailureRecords(job, evidence),
430
+ job.supportV5Budget || job.support_v5_budget || evidence.supportV5Budget || evidence.support_v5_budget,
431
+ now
432
+ );
433
+ }
434
+
435
+ function aicoderManagerFailureRecords(
436
+ app: Record<string, any>,
437
+ job: Record<string, any>,
438
+ evidence: Record<string, any>
439
+ ): ResolveIOAIManagerFailureRecord[] {
440
+ const records: ResolveIOAIManagerFailureRecord[] = [];
441
+ const pushRecord = (value: any, fallback: Partial<ResolveIOAIManagerFailureRecord> = {}): void => {
442
+ const record = normalizeManagerFailureRecord(value, {
443
+ lane: 'aicoder',
444
+ stepType: 'workflow_step',
445
+ ...fallback
446
+ });
447
+ if (record) {
448
+ records.push(record);
449
+ }
450
+ };
451
+ const historySources = [
452
+ job.aicoderV6StepHistory,
453
+ job.aiCoderV6StepHistory,
454
+ job.aicoder_v6_step_history,
455
+ job.ai_coder_v6_step_history,
456
+ job.workflowStepHistory,
457
+ job.workflow_step_history,
458
+ job.stepHistory,
459
+ job.step_history,
460
+ job.managerFailureHistory,
461
+ job.manager_failure_history,
462
+ app.aicoderV6StepHistory,
463
+ app.aiCoderV6StepHistory,
464
+ app.aicoder_v6_step_history,
465
+ app.ai_coder_v6_step_history,
466
+ app.workflowStepHistory,
467
+ app.workflow_step_history,
468
+ evidence.aicoderV6StepHistory,
469
+ evidence.aiCoderV6StepHistory,
470
+ evidence.aicoder_v6_step_history,
471
+ evidence.ai_coder_v6_step_history,
472
+ evidence.workflowStepHistory,
473
+ evidence.workflow_step_history,
474
+ evidence.stepHistory,
475
+ evidence.step_history,
476
+ evidence.managerFailureHistory,
477
+ evidence.manager_failure_history
478
+ ];
479
+ for (const source of historySources) {
480
+ for (const entry of asArray<any>(source)) {
481
+ pushRecord(entry);
482
+ }
483
+ }
484
+ const fingerprintSources = [
485
+ job.aicoderV6FailureFingerprints,
486
+ job.aiCoderV6FailureFingerprints,
487
+ job.aicoder_v6_failure_fingerprints,
488
+ job.ai_coder_v6_failure_fingerprints,
489
+ job.workflowFailureFingerprints,
490
+ job.workflow_failure_fingerprints,
491
+ job.failureFingerprints,
492
+ job.failure_fingerprints,
493
+ app.aicoderV6FailureFingerprints,
494
+ app.aiCoderV6FailureFingerprints,
495
+ app.aicoder_v6_failure_fingerprints,
496
+ app.ai_coder_v6_failure_fingerprints,
497
+ evidence.aicoderV6FailureFingerprints,
498
+ evidence.aiCoderV6FailureFingerprints,
499
+ evidence.aicoder_v6_failure_fingerprints,
500
+ evidence.ai_coder_v6_failure_fingerprints,
501
+ evidence.workflowFailureFingerprints,
502
+ evidence.workflow_failure_fingerprints,
503
+ evidence.failureFingerprints,
504
+ evidence.failure_fingerprints
505
+ ];
506
+ for (const source of fingerprintSources) {
507
+ for (const entry of asArray<any>(source)) {
508
+ const fingerprint = plainObject(entry);
509
+ const count = Math.max(1, Math.min(8, Number(fingerprint.count || fingerprint.repeatedCount || fingerprint.repeated_count || 1) || 1));
510
+ const fallback = {
511
+ lane: cleanText(fingerprint.lane || 'aicoder', 80),
512
+ stepType: cleanText(fingerprint.stepType || fingerprint.step_type || 'workflow_step', 80),
513
+ outcome: cleanText(fingerprint.outcome || 'needs_repair', 80),
514
+ failureClass: cleanText(fingerprint.failureClass || fingerprint.failure_class || fingerprint.class, 120),
515
+ blocker: cleanText(fingerprint.blocker || fingerprint.reason || fingerprint.summary, 1200),
516
+ blockerFingerprint: cleanText(fingerprint.blockerFingerprint || fingerprint.blocker_fingerprint, 180),
517
+ evidenceHash: cleanText(fingerprint.evidenceHash || fingerprint.evidence_hash, 180)
518
+ };
519
+ for (let index = 0; index < count; index += 1) {
520
+ pushRecord(fingerprint, fallback);
521
+ }
522
+ }
523
+ }
524
+ const currentFailure = evidenceObject(
525
+ job.aicoderV6CurrentFailure,
526
+ job.aiCoderV6CurrentFailure,
527
+ job.aicoder_v6_current_failure,
528
+ job.ai_coder_v6_current_failure,
529
+ job.workflowCurrentFailure,
530
+ job.workflow_current_failure,
531
+ job.currentFailure,
532
+ job.current_failure,
533
+ evidence.aicoderV6CurrentFailure,
534
+ evidence.aiCoderV6CurrentFailure,
535
+ evidence.aicoder_v6_current_failure,
536
+ evidence.ai_coder_v6_current_failure,
537
+ evidence.workflowCurrentFailure,
538
+ evidence.workflow_current_failure,
539
+ evidence.currentFailure,
540
+ evidence.current_failure
541
+ );
542
+ pushRecord(currentFailure);
543
+ return records.slice(-80);
544
+ }
545
+
546
+ function aicoderManagerNoBlindLoopGate(
547
+ app: Record<string, any>,
548
+ job: Record<string, any>,
549
+ evidence: Record<string, any>,
550
+ now?: Date | string
551
+ ): AIRunGateResult | undefined {
552
+ return managerNoBlindLoopGateFromHistory(
553
+ aicoderManagerFailureRecords(app, job, evidence),
554
+ job.aicoderV6Budget
555
+ || job.aiCoderV6Budget
556
+ || job.aicoder_v6_budget
557
+ || job.ai_coder_v6_budget
558
+ || evidence.aicoderV6Budget
559
+ || evidence.aiCoderV6Budget
560
+ || evidence.aicoder_v6_budget
561
+ || evidence.ai_coder_v6_budget
562
+ || app.aicoderV6Budget
563
+ || app.aiCoderV6Budget
564
+ || app.aicoder_v6_budget
565
+ || app.ai_coder_v6_budget
566
+ || job.budget
567
+ || evidence.budget,
568
+ now
569
+ );
570
+ }
571
+
572
+ function managerNoBlindLoopMetadata(gate: AIRunGateResult | undefined): Record<string, any> | undefined {
573
+ if (!gate) {
574
+ return undefined;
575
+ }
576
+ return {
577
+ action: cleanText(gate.metadata?.action, 120),
578
+ status: gate.status,
579
+ failureClass: cleanText(gate.metadata?.failureClass, 120),
580
+ sameFailureCount: Number(gate.metadata?.sameFailureCount || 0),
581
+ pingPongCount: Number(gate.metadata?.pingPongCount || 0),
582
+ newEvidence: gate.metadata?.newEvidence === true,
583
+ materialEvidence: gate.metadata?.materialEvidence === true,
584
+ evidenceStrength: cleanText(gate.metadata?.evidenceStrength, 120),
585
+ loopBudgetShouldReset: gate.metadata?.loopBudgetShouldReset === true,
586
+ dispatchAction: cleanText(gate.metadata?.dispatchAction, 160),
587
+ productRepairAllowed: gate.metadata?.productRepairAllowed === true,
588
+ expensiveModelAllowed: gate.metadata?.expensiveModelAllowed === true,
589
+ productRepairFailure: gate.metadata?.productRepairFailure === true,
590
+ requiresNewEvidence: gate.metadata?.requiresNewEvidence === true
591
+ };
592
+ }
593
+
594
+ function hotfixEvidenceCandidates(...sources: any[]): Record<string, any>[] {
595
+ const result: Record<string, any>[] = [];
596
+ const seen = new Set<string>();
597
+ const pushCandidate = (value: any): void => {
598
+ if (Array.isArray(value)) {
599
+ for (const entry of value) {
600
+ pushCandidate(entry);
601
+ }
602
+ return;
603
+ }
604
+ if (!value || typeof value !== 'object') {
605
+ return;
606
+ }
607
+ const nestedValues = [
608
+ value.hotfixEvidence,
609
+ value.hotfix_evidence,
610
+ value.releaseHotfixEvidence,
611
+ value.release_hotfix_evidence,
612
+ value.managerHotfixEvidence,
613
+ value.manager_hotfix_evidence,
614
+ value.backendHotfixEvidence,
615
+ value.backend_hotfix_evidence
616
+ ].filter(Boolean);
617
+ if (nestedValues.length) {
618
+ for (const nestedValue of nestedValues) {
619
+ pushCandidate(nestedValue);
620
+ }
621
+ return;
622
+ }
623
+ if (!(value.channel || value.hotfixChannel || value.hotfix_channel)) {
624
+ return;
625
+ }
626
+ const key = JSON.stringify({
627
+ channel: value.channel || value.hotfixChannel || value.hotfix_channel,
628
+ host: value.target?.host || value.host,
629
+ path: value.target?.path || value.path || value.remotePath || value.remote_path,
630
+ checksum: value.remoteChecksumAfter || value.remote_checksum_after || value.remoteChecksum || value.remote_checksum
631
+ });
632
+ if (seen.has(key)) {
633
+ return;
634
+ }
635
+ seen.add(key);
636
+ result.push(value);
637
+ };
638
+ for (const source of sources) {
639
+ pushCandidate(source);
640
+ }
641
+ return result;
642
+ }
643
+
644
+ function hotfixEvidenceArtifactPaths(evidence: Record<string, any>, normalized: Record<string, any> | undefined): string[] {
645
+ return [
646
+ normalized?.compiledArtifactPath,
647
+ normalized?.builtDistPath,
648
+ normalized?.target?.artifactPath,
649
+ evidence.compiledArtifactPath,
650
+ evidence.compiled_artifact_path,
651
+ evidence.builtDistPath,
652
+ evidence.built_dist_path,
653
+ evidence.artifactPath,
654
+ evidence.artifact_path
655
+ ].map((entry) => cleanText(entry, 500)).filter(Boolean).slice(0, 8);
656
+ }
657
+
658
+ function hotfixEvidenceGate(
659
+ evidence: Record<string, any>,
660
+ now?: Date | string
661
+ ): AIRunGateResult | undefined {
662
+ const validation = validateResolveIOAIManagerHotfixEvidence(evidence);
663
+ if (!validation.normalized && validation.status === 'missing') {
664
+ return undefined;
665
+ }
666
+ const status: AIRunGateResult['status'] = validation.valid
667
+ ? (validation.fullDeployAllowed ? 'warn' : 'pass')
668
+ : validation.status === 'blocked'
669
+ ? 'blocked'
670
+ : 'fail';
671
+ const reason = validation.valid
672
+ ? validation.fullDeployAllowed
673
+ ? 'Hotfix evidence allows exactly one full deploy after force/new-artifact proof.'
674
+ : 'Hotfix evidence is sufficient; rerun the smallest release gate before continuing.'
675
+ : validation.blockers.join('; ');
676
+ return {
677
+ key: 'hotfix_evidence',
678
+ label: 'Hotfix evidence',
679
+ status,
680
+ reason,
681
+ evidenceRefs: hotfixEvidenceArtifactPaths(evidence, validation.normalized as any),
682
+ recordedAt: isoNow(now || validation.normalized?.recordedAt),
683
+ metadata: {
684
+ channel: validation.channel,
685
+ nextAction: validation.nextAction,
686
+ fullDeployAllowed: validation.fullDeployAllowed,
687
+ fullDeployBlocked: validation.fullDeployBlocked,
688
+ hotfixSatisfied: validation.hotfixSatisfied,
689
+ blockers: validation.blockers,
690
+ githubCommitGuard: validation.githubCommitGuard,
691
+ target: validation.normalized?.target
692
+ }
693
+ };
694
+ }
695
+
696
+ function hotfixCommitProofGate(
697
+ evidence: Record<string, any>,
698
+ now?: Date | string
699
+ ): AIRunGateResult | undefined {
700
+ const validation = validateResolveIOAIManagerHotfixEvidence(evidence);
701
+ const guard = validation.githubCommitGuard;
702
+ if (!guard.required) {
703
+ return undefined;
704
+ }
705
+ const status: AIRunGateResult['status'] = guard.passed
706
+ ? 'pass'
707
+ : guard.status === 'invalid'
708
+ ? 'fail'
709
+ : 'blocked';
710
+ return {
711
+ key: 'hotfix_commit_proof',
712
+ label: 'Hotfix GitHub commit proof',
713
+ status,
714
+ reason: guard.passed
715
+ ? 'Hotfix has matching sourceCommitSha, GitHub commit URL, passed gitCommitStatus, and passed gitPushStatus.'
716
+ : (guard.blockers.join('; ') || 'Manager hotfix is blocked until sourceCommitSha, githubCommitUrl, passed gitCommitStatus, and passed gitPushStatus prove the hotfix is committed and pushed to GitHub.'),
717
+ evidenceRefs: cleanStringList([guard.githubCommitUrl], 1, 500),
718
+ recordedAt: isoNow(now || validation.normalized?.recordedAt),
719
+ metadata: {
720
+ channel: guard.channel || validation.channel,
721
+ status: guard.status,
722
+ passed: guard.passed,
723
+ commitFirstProtocol: guard.required ? 'commit_first_hotfix' : 'not_required',
724
+ commitFirstProtocolSatisfied: guard.required ? guard.passed === true : true,
725
+ liveHotfixBlockedUntilCommit: guard.required === true && guard.passed !== true,
726
+ managerMustCommitBeforeHotfix: guard.managerMustCommitBeforeHotfix,
727
+ sourceCommitSha: guard.sourceCommitSha,
728
+ githubCommitUrl: guard.githubCommitUrl,
729
+ githubCommitSha: guard.githubCommitSha,
730
+ gitCommitStatus: guard.gitCommitStatus,
731
+ gitPushStatus: guard.gitPushStatus,
732
+ blockers: guard.blockers,
733
+ nextCommands: guard.nextCommands,
734
+ requiredEvidence: guard.requiredEvidence
735
+ }
736
+ };
737
+ }
738
+
739
+ function hotfixDurabilityContractCandidates(...sources: any[]): Record<string, any>[] {
740
+ const result: Record<string, any>[] = [];
741
+ const seen = new Set<string>();
742
+ const pushCandidate = (value: any): void => {
743
+ if (Array.isArray(value)) {
744
+ for (const entry of value) {
745
+ pushCandidate(entry);
746
+ }
747
+ return;
748
+ }
749
+ if (!value || typeof value !== 'object') {
750
+ return;
751
+ }
752
+ const nestedValues = [
753
+ value.hotfixDurabilityContract,
754
+ value.hotfix_durability_contract,
755
+ value.supportManagerHotfixDurabilityContract,
756
+ value.support_manager_hotfix_durability_contract,
757
+ value.managerRecoveryHotfixDurability,
758
+ value.manager_recovery_hotfix_durability,
759
+ value.supportV5RecoveryDirective?.hotfixDurabilityContract,
760
+ value.supportV5RecoveryDirective?.hotfix_durability_contract
761
+ ].filter(Boolean);
762
+ if (nestedValues.length) {
763
+ for (const nestedValue of nestedValues) {
764
+ pushCandidate(nestedValue);
765
+ }
766
+ }
767
+ const required = value.required === true || value.liveHotfixBlockedUntilCommit === true || value.live_hotfix_blocked_until_commit === true || value.commitProofPassed === true || value.commit_proof_passed === true;
768
+ if (!required) {
769
+ return;
770
+ }
771
+ const key = cleanText(value.contractId || value.contract_id || JSON.stringify({
772
+ status: value.status,
773
+ sha: value.sourceCommitSha || value.source_commit_sha,
774
+ url: value.githubCommitUrl || value.github_commit_url
775
+ }), 500);
776
+ if (seen.has(key)) {
777
+ return;
778
+ }
779
+ seen.add(key);
780
+ result.push(value);
781
+ };
782
+ for (const source of sources) {
783
+ pushCandidate(source);
784
+ }
785
+ return result;
786
+ }
787
+
788
+ function hotfixDurabilityContractGate(
789
+ contractValue: Record<string, any>,
790
+ now?: Date | string
791
+ ): AIRunGateResult | undefined {
792
+ const contract = buildResolveIOAIManagerHotfixDurabilityContract({
793
+ existing: contractValue,
794
+ evidence: contractValue.evidence || contractValue.hotfixEvidence || contractValue.hotfix_evidence,
795
+ action: contractValue.action,
796
+ reason: contractValue.reason,
797
+ now
798
+ });
799
+ if (!contract.required) {
800
+ return undefined;
801
+ }
802
+ const status: AIRunGateResult['status'] = contract.liveHotfixBlockedUntilCommit
803
+ ? 'blocked'
804
+ : contract.status === 'ready_for_continuation' || contract.status === 'ready_for_release_gate'
805
+ ? 'pass'
806
+ : 'warn';
807
+ return {
808
+ key: 'hotfix_durability_contract',
809
+ label: 'Hotfix durability contract',
810
+ status,
811
+ reason: contract.liveHotfixBlockedUntilCommit
812
+ ? `Manager hotfix is blocked until GitHub commit proof is recorded: ${contract.missingCommitProofFields.join(', ') || 'sourceCommitSha, githubCommitUrl, gitCommitStatus=passed, gitPushStatus=passed'}`
813
+ : `Manager hotfix durability contract is ${contract.status}.`,
814
+ evidenceRefs: cleanStringList([contract.githubCommitUrl], 1, 500),
815
+ recordedAt: isoNow(now || contract.updatedAt),
816
+ metadata: {
817
+ contractId: contract.contractId,
818
+ status: contract.status,
819
+ nextSafeAction: contract.nextSafeAction,
820
+ canPrepareHotfixPatch: contract.canPrepareHotfixPatch,
821
+ canHotfixBackend: contract.canHotfixBackend,
822
+ liveHotfixBlockedUntilCommit: contract.liveHotfixBlockedUntilCommit,
823
+ durabilityProtocol: contract.durabilityProtocol,
824
+ commitFirstProtocolRequired: contract.commitFirstProtocolRequired,
825
+ commitFirstProtocolSatisfied: contract.commitFirstProtocolSatisfied,
826
+ liveHotfixAllowed: contract.liveHotfixAllowed,
827
+ managerContinuationAllowed: contract.managerContinuationAllowed,
828
+ commitProofPassed: contract.commitProofPassed,
829
+ commitProofStatus: contract.commitProofStatus,
830
+ missingCommitProofFields: contract.missingCommitProofFields,
831
+ sourceCommitSha: contract.sourceCommitSha,
832
+ githubCommitUrl: contract.githubCommitUrl,
833
+ gitCommitStatus: contract.gitCommitStatus,
834
+ gitPushStatus: contract.gitPushStatus,
835
+ phaseOrder: contract.phaseOrder,
836
+ requiredFields: contract.requiredFields,
837
+ forbiddenActions: contract.forbiddenActions
838
+ }
839
+ };
840
+ }
841
+
842
+ function collectHotfixEvidenceEvents(
843
+ sources: any[],
844
+ events: AIRunEvent[],
845
+ gates: AIRunGateResult[],
846
+ now?: Date | string
847
+ ): void {
848
+ for (const contract of hotfixDurabilityContractCandidates(...sources)) {
849
+ const gate = hotfixDurabilityContractGate(contract, now);
850
+ if (gate) {
851
+ gates.push(gate);
852
+ pushEvent(events, {
853
+ type: 'log',
854
+ category: 'hotfix_durability_contract',
855
+ message: gate.reason,
856
+ artifactPaths: gate.evidenceRefs,
857
+ recordedAt: gate.recordedAt,
858
+ metadata: gate.metadata
859
+ });
860
+ }
861
+ }
862
+ for (const evidence of hotfixEvidenceCandidates(...sources)) {
863
+ const validation = validateResolveIOAIManagerHotfixEvidence(evidence);
864
+ if (!validation.normalized && validation.status === 'missing') {
865
+ continue;
866
+ }
867
+ const artifactPaths = hotfixEvidenceArtifactPaths(evidence, validation.normalized as any);
868
+ pushEvent(events, {
869
+ type: 'hotfix',
870
+ category: validation.channel || 'hotfix',
871
+ message: validation.valid
872
+ ? `Hotfix evidence recorded for ${validation.channel}.`
873
+ : `Incomplete hotfix evidence for ${validation.channel || 'unknown channel'}.`,
874
+ artifactPaths,
875
+ recordedAt: validation.normalized?.recordedAt || eventDate(evidence),
876
+ metadata: {
877
+ status: validation.status,
878
+ nextAction: validation.nextAction,
879
+ fullDeployAllowed: validation.fullDeployAllowed,
880
+ fullDeployBlocked: validation.fullDeployBlocked,
881
+ hotfixSatisfied: validation.hotfixSatisfied,
882
+ blockers: validation.blockers,
883
+ githubCommitGuard: validation.githubCommitGuard,
884
+ target: validation.normalized?.target
885
+ }
886
+ });
887
+ const gate = hotfixEvidenceGate(evidence, now);
888
+ if (gate) {
889
+ gates.push(gate);
890
+ }
891
+ const commitProofGate = hotfixCommitProofGate(evidence, now);
892
+ if (commitProofGate) {
893
+ gates.push(commitProofGate);
894
+ pushEvent(events, {
895
+ type: 'log',
896
+ category: 'hotfix_commit_proof',
897
+ message: commitProofGate.reason,
898
+ artifactPaths: commitProofGate.evidenceRefs,
899
+ recordedAt: commitProofGate.recordedAt,
900
+ metadata: commitProofGate.metadata
901
+ });
902
+ }
903
+ }
904
+ }
905
+
906
+ function hotfixDurabilityMetadata(gates: AIRunGateResult[]): Record<string, any> | undefined {
907
+ const hotfixGate = gates.find((gate) => gate.key === 'hotfix_evidence');
908
+ const commitGate = gates.find((gate) => gate.key === 'hotfix_commit_proof');
909
+ const durabilityGate = gates.find((gate) => gate.key === 'hotfix_durability_contract');
910
+ if (!hotfixGate && !commitGate && !durabilityGate) {
911
+ return undefined;
912
+ }
913
+ const commitBlocked = (!!commitGate && (commitGate.status === 'blocked' || commitGate.status === 'fail'))
914
+ || durabilityGate?.metadata?.liveHotfixBlockedUntilCommit === true;
915
+ return {
916
+ durabilityStatus: cleanText(durabilityGate?.metadata?.status, 80),
917
+ durabilityGateStatus: cleanText(durabilityGate?.status, 80),
918
+ canPrepareHotfixPatch: durabilityGate?.metadata?.canPrepareHotfixPatch === true,
919
+ canHotfixBackend: durabilityGate?.metadata?.canHotfixBackend === true,
920
+ liveHotfixBlockedUntilCommit: durabilityGate?.metadata?.liveHotfixBlockedUntilCommit === true,
921
+ durabilityProtocol: cleanText(durabilityGate?.metadata?.durabilityProtocol, 120),
922
+ commitFirstProtocolRequired: durabilityGate?.metadata?.commitFirstProtocolRequired === true,
923
+ commitFirstProtocolSatisfied: durabilityGate?.metadata?.commitFirstProtocolSatisfied === true || commitGate?.metadata?.commitFirstProtocolSatisfied === true,
924
+ liveHotfixAllowed: durabilityGate?.metadata?.liveHotfixAllowed === true || (hotfixGate?.metadata?.hotfixSatisfied === true && commitGate?.metadata?.commitFirstProtocolSatisfied === true),
925
+ managerContinuationAllowed: durabilityGate?.metadata?.managerContinuationAllowed === true,
926
+ hotfixEvidenceStatus: cleanText(hotfixGate?.status, 80),
927
+ hotfixSatisfied: hotfixGate?.metadata?.hotfixSatisfied === true,
928
+ fullDeployBlocked: hotfixGate?.metadata?.fullDeployBlocked === true,
929
+ fullDeployAllowed: hotfixGate?.metadata?.fullDeployAllowed === true,
930
+ commitProofRequired: !!commitGate || !!durabilityGate,
931
+ commitProofStatus: cleanText(commitGate?.status || durabilityGate?.metadata?.commitProofStatus, 80),
932
+ commitProofPassed: commitGate?.metadata?.passed === true || durabilityGate?.metadata?.commitProofPassed === true,
933
+ commitBlocked,
934
+ channel: cleanText(commitGate?.metadata?.channel || hotfixGate?.metadata?.channel, 120),
935
+ sourceCommitSha: cleanText(commitGate?.metadata?.sourceCommitSha || durabilityGate?.metadata?.sourceCommitSha, 200),
936
+ githubCommitUrl: cleanText(commitGate?.metadata?.githubCommitUrl || durabilityGate?.metadata?.githubCommitUrl, 500),
937
+ gitCommitStatus: cleanText(commitGate?.metadata?.gitCommitStatus || durabilityGate?.metadata?.gitCommitStatus, 120),
938
+ gitPushStatus: cleanText(commitGate?.metadata?.gitPushStatus || durabilityGate?.metadata?.gitPushStatus, 120),
939
+ blockers: cleanStringList([
940
+ ...asArray(durabilityGate?.metadata?.missingCommitProofFields),
941
+ ...asArray(commitGate?.metadata?.blockers),
942
+ ...asArray(hotfixGate?.metadata?.blockers)
943
+ ], 20, 500),
944
+ requiredEvidence: cleanStringList(commitGate?.metadata?.requiredEvidence || durabilityGate?.metadata?.requiredFields, 20, 500),
945
+ nextCommands: cleanStringList(commitGate?.metadata?.nextCommands, 20, 240),
946
+ nextAction: commitBlocked
947
+ ? cleanText(durabilityGate?.metadata?.nextSafeAction, 200) || 'Commit and push the hotfix to GitHub, record sourceCommitSha/githubCommitUrl/gitCommitStatus/gitPushStatus, then rerun the smallest verification gate.'
948
+ : cleanText(hotfixGate?.metadata?.nextAction, 1000)
949
+ };
950
+ }
951
+
952
+ function collectUsageEvents(entries: Array<Record<string, any>>, events: AIRunEvent[]): AIRunCost | undefined {
953
+ if (!entries.length) {
954
+ return undefined;
955
+ }
956
+ const normalizedEntries = normalizeOpenAIUsageRowsForCosting(entries);
957
+ for (const entry of normalizedEntries) {
958
+ pushEvent(events, {
959
+ type: 'usage',
960
+ category: cleanText(entry.category || entry.kind || entry.phase, 160) || 'usage',
961
+ message: cleanText(entry.summary || entry.description || entry.model || 'Model usage recorded.', 800),
962
+ recordedAt: eventDate(entry),
963
+ metadata: {
964
+ model: entry.model,
965
+ inputTokens: entry.inputTokens ?? entry.input_tokens,
966
+ outputTokens: entry.outputTokens ?? entry.output_tokens,
967
+ totalTokens: entry.totalTokens ?? entry.total_tokens,
968
+ estimatedUsd: entry.estimatedUsd ?? entry.cost_estimate,
969
+ usageSource: entry.usage_source ?? entry.usageSource,
970
+ usageSurface: entry.usage_surface ?? entry.usageSurface,
971
+ usagePhase: entry.usage_phase ?? entry.usagePhase,
972
+ costBasis: entry.cost_basis ?? entry.costBasis,
973
+ isManual: entry.is_manual ?? entry.isManual,
974
+ isUntracked: entry.is_untracked ?? entry.isUntracked,
975
+ untrackedReason: entry.untracked_reason ?? entry.untrackedReason
976
+ }
977
+ });
978
+ }
979
+ return buildAIRunCost(entries);
980
+ }
981
+
982
+ function costObservabilityGate(cost: AIRunCost | undefined, source: AIRun['source'], now?: Date | string): AIRunGateResult {
983
+ const manualEstimatedUsd = Number(cost?.manualEstimatedUsd || 0);
984
+ const untrackedEstimatedUsd = Number(cost?.untrackedEstimatedUsd || 0);
985
+ const warnings = cleanStringList(cost?.untrackedWarnings, 20, 500);
986
+ const manualSources = Object.entries(cost?.sources || {})
987
+ .filter(([, value]) => Number(value.manualCount || 0) > 0)
988
+ .map(([key]) => key);
989
+ const untrackedSources = Object.entries(cost?.sources || {})
990
+ .filter(([, value]) => Number(value.untrackedCount || 0) > 0)
991
+ .map(([key]) => key);
992
+ const missingLedger = !cost;
993
+ const externalCodexMonitorUsed = manualEstimatedUsd > 0
994
+ || untrackedEstimatedUsd > 0
995
+ || manualSources.some((sourceKey) => /codex|manual/i.test(sourceKey))
996
+ || warnings.some((warning) => /codex|sidecar|monitor/i.test(warning));
997
+ const status: AIRunGateResult['status'] = missingLedger
998
+ ? 'skipped'
999
+ : (manualEstimatedUsd > 0 || untrackedEstimatedUsd > 0 || warnings.length ? 'warn' : 'pass');
1000
+ return {
1001
+ key: 'cost_observability',
1002
+ label: 'Cost observability',
1003
+ status,
1004
+ reason: missingLedger
1005
+ ? 'No usage ledger was supplied for this run, so cost per accepted run cannot be fully evaluated.'
1006
+ : status === 'warn'
1007
+ ? `Runner cost includes manual or untracked usage: manual=$${manualEstimatedUsd.toFixed(2)}, untracked=$${untrackedEstimatedUsd.toFixed(2)}.`
1008
+ : 'Runner usage is tracked in the unified cost ledger.',
1009
+ evidenceRefs: warnings,
1010
+ recordedAt: isoNow(now),
1011
+ metadata: {
1012
+ source,
1013
+ missingLedger,
1014
+ estimatedUsd: Number(cost?.estimatedUsd || 0),
1015
+ manualEstimatedUsd,
1016
+ untrackedEstimatedUsd,
1017
+ externalCodexMonitorUsed,
1018
+ manualSources,
1019
+ untrackedSources,
1020
+ models: cleanStringList(cost?.models, 40, 120),
1021
+ categories: Object.keys(cost?.categories || {}).slice(0, 40),
1022
+ surfaces: Object.keys(cost?.surfaces || {}).slice(0, 40),
1023
+ warnings
1024
+ }
1025
+ };
1026
+ }
1027
+
1028
+ function costObservabilityMetadata(gate: AIRunGateResult | undefined): Record<string, any> | undefined {
1029
+ if (!gate) {
1030
+ return undefined;
1031
+ }
1032
+ return {
1033
+ status: gate.status,
1034
+ missingLedger: gate.metadata?.missingLedger === true,
1035
+ estimatedUsd: Number(gate.metadata?.estimatedUsd || 0),
1036
+ manualEstimatedUsd: Number(gate.metadata?.manualEstimatedUsd || 0),
1037
+ untrackedEstimatedUsd: Number(gate.metadata?.untrackedEstimatedUsd || 0),
1038
+ externalCodexMonitorUsed: gate.metadata?.externalCodexMonitorUsed === true,
1039
+ manualSources: cleanStringList(gate.metadata?.manualSources, 20, 120),
1040
+ untrackedSources: cleanStringList(gate.metadata?.untrackedSources, 20, 120),
1041
+ models: cleanStringList(gate.metadata?.models, 40, 120),
1042
+ categories: cleanStringList(gate.metadata?.categories, 40, 120),
1043
+ surfaces: cleanStringList(gate.metadata?.surfaces, 40, 120),
1044
+ warnings: cleanStringList(gate.metadata?.warnings, 20, 500),
1045
+ nextAction: gate.status === 'warn'
1046
+ ? 'Record manual Codex sidecar usage in the unified ledger and replace external monitoring with a validated next-action contract.'
1047
+ : gate.status === 'skipped'
1048
+ ? 'Attach runner, assistant, manual Codex, retry, and deploy usage rows before comparing cost per accepted run.'
1049
+ : 'Continue tracking cost in the unified ledger.'
1050
+ };
1051
+ }
1052
+
1053
+ function collectCommitEvents(commits: Array<Record<string, any>>, events: AIRunEvent[]): void {
1054
+ for (const commit of commits.slice(0, 80)) {
1055
+ pushEvent(events, {
1056
+ type: 'git_commit',
1057
+ message: cleanText(commit.message || commit.subject || commit.title || commit.hash || commit.sha, 1200),
1058
+ recordedAt: eventDate(commit),
1059
+ metadata: {
1060
+ sha: commit.sha || commit.hash,
1061
+ branch: commit.branch,
1062
+ author: commit.author
1063
+ }
1064
+ });
1065
+ }
1066
+ }
1067
+
1068
+ function statusFromBoolean(value: any, fallback?: string): string | undefined {
1069
+ if (value === true) {
1070
+ return 'pass';
1071
+ }
1072
+ if (value === false) {
1073
+ return 'fail';
1074
+ }
1075
+ return fallback;
1076
+ }
1077
+
1078
+ function collectArtifacts(evidence: Record<string, any> | undefined): AIQaArtifact[] {
1079
+ const artifacts: AIQaArtifact[] = [];
1080
+ for (const entry of asArray<any>(evidence?.artifacts)) {
1081
+ artifacts.push({
1082
+ type: entry.type || 'other',
1083
+ path: cleanText(entry.path || entry.file || entry.artifactPath, 500),
1084
+ url: cleanText(entry.url, 500),
1085
+ title: cleanText(entry.title || entry.name, 300),
1086
+ caption: cleanText(entry.caption || entry.message, 800),
1087
+ metadata: entry.metadata
1088
+ });
1089
+ }
1090
+ const screenshots = asArray<any>(evidence?.screenshots || evidence?.qaScreenshots || evidence?.browserScreenshots);
1091
+ for (const screenshot of screenshots.slice(0, 40)) {
1092
+ if (typeof screenshot === 'string') {
1093
+ artifacts.push({ type: 'screenshot', path: screenshot });
1094
+ } else {
1095
+ artifacts.push({
1096
+ type: 'screenshot',
1097
+ path: cleanText(screenshot.path || screenshot.file, 500),
1098
+ url: cleanText(screenshot.url, 500),
1099
+ title: cleanText(screenshot.title || screenshot.route, 300),
1100
+ caption: cleanText(screenshot.caption || screenshot.message, 800),
1101
+ metadata: screenshot.metadata
1102
+ });
1103
+ }
1104
+ }
1105
+ const artifactRefs = [
1106
+ ...asArray<any>(evidence?.artifactPaths),
1107
+ ...asArray<any>(evidence?.artifact_paths),
1108
+ ...asArray<any>(evidence?.runnerEvidenceArtifacts),
1109
+ ...asArray<any>(evidence?.runner_evidence_artifacts),
1110
+ ...asArray<any>(evidence?.supportQaArtifacts),
1111
+ ...asArray<any>(evidence?.support_qa_artifacts),
1112
+ ...asArray<any>(evidence?.qa_artifacts)
1113
+ ];
1114
+ for (const entry of artifactRefs.slice(0, 60)) {
1115
+ if (typeof entry === 'string') {
1116
+ artifacts.push({ type: 'other', path: cleanText(entry, 500) });
1117
+ }
1118
+ else if (entry && typeof entry === 'object') {
1119
+ artifacts.push({
1120
+ type: entry.type || 'other',
1121
+ path: cleanText(entry.path || entry.file || entry.artifactPath || entry.artifact_path, 500),
1122
+ url: cleanText(entry.url, 500),
1123
+ title: cleanText(entry.title || entry.name || entry.label, 300),
1124
+ caption: cleanText(entry.caption || entry.message || entry.summary, 800),
1125
+ metadata: entry.metadata
1126
+ });
1127
+ }
1128
+ }
1129
+ return artifacts;
1130
+ }
1131
+
1132
+ function collectInfraChecks(evidence: Record<string, any> | undefined): AIQaInfraCheck[] {
1133
+ const checks: AIQaInfraCheck[] = [];
1134
+ for (const entry of asArray<any>(evidence?.infraChecks || evidence?.preflightChecks)) {
1135
+ checks.push({
1136
+ name: cleanText(entry.name || entry.check || entry.key, 160) || 'infra_check',
1137
+ status: entry.status || statusFromBoolean(entry.passed),
1138
+ message: cleanText(entry.message || entry.error || entry.reason, 1000),
1139
+ path: cleanText(entry.path, 500),
1140
+ durationMs: Number(entry.durationMs ?? entry.duration_ms) || undefined,
1141
+ metadata: entry.metadata,
1142
+ checkedAt: eventDate(entry)
1143
+ });
1144
+ }
1145
+ if (evidence?.preflightStatus) {
1146
+ checks.push({
1147
+ name: 'qa_preflight',
1148
+ status: evidence.preflightStatus,
1149
+ message: cleanText(evidence.preflightMessage || evidence.preflightReason, 1000),
1150
+ path: cleanText(evidence.preflightArtifactPath || evidence.preflightPath, 500)
1151
+ });
1152
+ }
1153
+ return checks;
1154
+ }
1155
+
1156
+ function collectCompileResult(evidence: Record<string, any> | undefined): AIQaCompileResult | undefined {
1157
+ const build = evidence?.compile || evidence?.build || evidence?.buildEvidence || evidence?.buildResult;
1158
+ if (!build && evidence?.compileStatus === undefined && evidence?.buildStatus === undefined) {
1159
+ return undefined;
1160
+ }
1161
+ const status = build?.status
1162
+ || evidence?.compileStatus
1163
+ || evidence?.buildStatus
1164
+ || statusFromBoolean(build?.passed ?? build?.allExpectedPassed ?? evidence?.buildPassed);
1165
+ return {
1166
+ status,
1167
+ command: cleanText(build?.command || evidence?.buildCommand, 500),
1168
+ artifactPath: cleanText(build?.artifactPath || build?.logPath || evidence?.buildLogPath, 500),
1169
+ message: cleanText(build?.message || build?.error || evidence?.buildMessage, 1000),
1170
+ durationMs: Number(build?.durationMs ?? build?.duration_ms) || undefined,
1171
+ staleEvidence: build?.staleEvidence === true || evidence?.staleBuildEvidence === true,
1172
+ recordedAt: eventDate(build || evidence || {})
1173
+ };
1174
+ }
1175
+
1176
+ function collectRouteProbes(evidence: Record<string, any> | undefined): AIQaRouteProbe[] {
1177
+ const probes: AIQaRouteProbe[] = [];
1178
+ const rows = [
1179
+ ...asArray<any>(evidence?.routeProbes),
1180
+ ...asArray<any>(evidence?.route_probes),
1181
+ ...asArray<any>(evidence?.routes),
1182
+ ...asArray<any>(evidence?.browserRoutes),
1183
+ ...asArray<any>(evidence?.browser_routes),
1184
+ ...asArray<any>(evidence?.workflowProbes),
1185
+ ...asArray<any>(evidence?.workflow_probes)
1186
+ ];
1187
+ const directProbe = plainObject(evidence?.routeProbe || evidence?.route_probe || evidence?.workflowProbe || evidence?.workflow_probe);
1188
+ if (Object.keys(directProbe).length) {
1189
+ rows.push(directProbe);
1190
+ }
1191
+ for (const entry of rows) {
1192
+ probes.push({
1193
+ route: cleanText(entry.route || entry.path || entry.url, 500) || '/',
1194
+ status: entry.status || statusFromBoolean(entry.passed),
1195
+ screenshot: cleanText(entry.screenshot || entry.screenshotPath, 500),
1196
+ caption: cleanText(entry.caption || entry.message, 1000),
1197
+ shellOnly: entry.shellOnly === true,
1198
+ authenticated: entry.authenticated === true,
1199
+ consoleErrors: asArray<string>(entry.consoleErrors),
1200
+ networkErrors: asArray<string>(entry.networkErrors),
1201
+ message: cleanText(entry.message || entry.error, 1000),
1202
+ recordedAt: eventDate(entry)
1203
+ });
1204
+ }
1205
+ const screenshots = asArray<any>(evidence?.screenshots || evidence?.qaScreenshots || evidence?.browserScreenshots);
1206
+ for (const screenshot of screenshots.slice(0, 20)) {
1207
+ if (typeof screenshot === 'string') {
1208
+ probes.push({ route: '/', status: 'pass', screenshot, caption: 'Historical browser screenshot recorded.' });
1209
+ } else if (screenshot.route || screenshot.path || screenshot.url || screenshot.screenshot || screenshot.file) {
1210
+ probes.push({
1211
+ route: cleanText(screenshot.route || screenshot.url || screenshot.path, 500) || '/',
1212
+ status: screenshot.status || statusFromBoolean(screenshot.passed, 'pass'),
1213
+ screenshot: cleanText(screenshot.screenshot || screenshot.screenshotPath || screenshot.file || screenshot.path, 500),
1214
+ caption: cleanText(screenshot.caption || screenshot.message, 1000),
1215
+ shellOnly: screenshot.shellOnly === true,
1216
+ authenticated: screenshot.authenticated === true,
1217
+ recordedAt: eventDate(screenshot)
1218
+ });
1219
+ }
1220
+ }
1221
+ return probes;
1222
+ }
1223
+
1224
+ function collectBusinessAssertions(evidence: Record<string, any> | undefined): AIQaBusinessAssertion[] {
1225
+ const assertions: AIQaBusinessAssertion[] = [];
1226
+ const rows = [
1227
+ ...asArray<any>(evidence?.businessAssertions),
1228
+ ...asArray<any>(evidence?.business_assertions),
1229
+ ...asArray<any>(evidence?.workflowAssertions),
1230
+ ...asArray<any>(evidence?.workflow_assertions),
1231
+ ...asArray<any>(evidence?.assertions),
1232
+ ...asArray<any>(evidence?.qaRows),
1233
+ ...asArray<any>(evidence?.qa_rows),
1234
+ ...asArray<any>(evidence?.rows),
1235
+ ...asArray<any>(evidence?.supportQaAssertions),
1236
+ ...asArray<any>(evidence?.support_qa_assertions),
1237
+ ...asArray<any>(evidence?.aiQaBusinessAssertions),
1238
+ ...asArray<any>(evidence?.ai_qa_business_assertions)
1239
+ ];
1240
+ for (const entry of rows) {
1241
+ assertions.push({
1242
+ assertion: cleanText(entry.assertion || entry.name || entry.label || entry.workflow || entry.expected, 1000) || 'business assertion',
1243
+ status: entry.status || entry.outcome || entry.result || statusFromBoolean(entry.passed),
1244
+ workflow: cleanText(entry.workflow || entry.workflowName || entry.workflow_name, 400),
1245
+ route: cleanText(entry.route || entry.url, 500),
1246
+ action: cleanText(entry.action || entry.action_under_test || entry.actionUnderTest, 500),
1247
+ expected: cleanText(entry.expected || entry.expected_business_state_change || entry.expectedBusinessStateChange, 1000),
1248
+ observed: cleanText(entry.observed || entry.actual || entry.after || entry.after_state || entry.afterState, 1000),
1249
+ dataProof: cleanText(entry.dataProof || entry.data_proof || entry.domProof || entry.dom_proof || entry.proof || entry.summary, 1400),
1250
+ mongoDelta: plainObject(entry.mongoDelta || entry.mongo_delta),
1251
+ artifactPaths: cleanStringList(entry.artifactPaths || entry.artifact_paths || entry.artifacts || entry.screenshots || (entry.screenshot ? [entry.screenshot] : []), 30, 500),
1252
+ message: cleanText(entry.message || entry.error || entry.reason, 1000),
1253
+ recordedAt: eventDate(entry),
1254
+ metadata: {
1255
+ ...plainObject(entry.metadata),
1256
+ ...(entry.acceptanceBlocked === true || entry.acceptance_blocked === true ? { acceptanceBlocked: true } : {}),
1257
+ ...(entry.routeOnly === true || entry.route_only === true ? { routeOnly: true } : {})
1258
+ }
1259
+ });
1260
+ }
1261
+ if (!assertions.length && evidence?.qaRunOutcome === 'business_assertion_passed') {
1262
+ assertions.push({
1263
+ assertion: cleanText(evidence.aiRunOutcomeReason || evidence.reason || 'Historical business assertion passed.', 1000),
1264
+ status: 'pass',
1265
+ dataProof: cleanText(evidence.aiRunOutcomeReason || evidence.reason, 1400)
1266
+ });
1267
+ }
1268
+ if (!assertions.length && evidence?.qaRunOutcome === 'business_assertion_failed') {
1269
+ assertions.push({
1270
+ assertion: cleanText(evidence.aiRunOutcomeReason || evidence.reason || 'Historical business assertion failed.', 1000),
1271
+ status: 'fail',
1272
+ observed: cleanText(evidence.aiRunOutcomeReason || evidence.reason, 1000)
1273
+ });
1274
+ }
1275
+ return assertions;
1276
+ }
1277
+
1278
+ function buildQaFromEvidence(evidence: Record<string, any> | undefined, now?: Date | string): AIQaRun {
1279
+ return buildAIQaRun({
1280
+ infraChecks: collectInfraChecks(evidence),
1281
+ compile: collectCompileResult(evidence),
1282
+ routeProbes: collectRouteProbes(evidence),
1283
+ businessAssertions: collectBusinessAssertions(evidence),
1284
+ artifacts: collectArtifacts(evidence),
1285
+ now
1286
+ });
1287
+ }
1288
+
1289
+ function isoNow(value?: Date | string): string {
1290
+ if (value instanceof Date) {
1291
+ return value.toISOString();
1292
+ }
1293
+ if (value) {
1294
+ const parsed = new Date(value);
1295
+ if (!Number.isNaN(parsed.getTime())) {
1296
+ return parsed.toISOString();
1297
+ }
1298
+ }
1299
+ return new Date().toISOString();
1300
+ }
1301
+
1302
+ function normalizeMatchText(value: any): string {
1303
+ return cleanText(value, 3000)
1304
+ .toLowerCase()
1305
+ .replace(/[^a-z0-9]+/g, ' ')
1306
+ .replace(/\s+/g, ' ')
1307
+ .trim();
1308
+ }
1309
+
1310
+ function importantTokens(value: any): Set<string> {
1311
+ const stopWords = new Set([
1312
+ 'the', 'and', 'that', 'with', 'from', 'after', 'before', 'when', 'then', 'this', 'must',
1313
+ 'should', 'will', 'only', 'into', 'have', 'has', 'are', 'was', 'were', 'been'
1314
+ ]);
1315
+ return new Set(normalizeMatchText(value).split(' ')
1316
+ .filter((token) => token.length >= 4 && !stopWords.has(token)));
1317
+ }
1318
+
1319
+ function businessAssertionPassed(assertion: AIQaBusinessAssertion): boolean {
1320
+ return /^(pass|passed|success|ok|done)$/i.test(cleanText(assertion.status, 40));
1321
+ }
1322
+
1323
+ function supportAssertionMatchesDiagnosisProof(assertion: AIQaBusinessAssertion, proofPlan: Record<string, any>): boolean {
1324
+ const metadata = assertion.metadata || {};
1325
+ if (metadata.diagnosisProofPlanMatched === true || metadata.proofPlanMatched === true || metadata.supportDiagnosisProof === true) {
1326
+ return true;
1327
+ }
1328
+ const requiredBusinessAssertion = proofPlan.business_assertion || proofPlan.businessAssertion;
1329
+ const proofParts = [
1330
+ requiredBusinessAssertion,
1331
+ proofPlan.after,
1332
+ proofPlan.data_assertion || proofPlan.dataAssertion,
1333
+ proofPlan.artifact_expectation || proofPlan.artifactExpectation
1334
+ ].map((part) => normalizeMatchText(part)).filter((part) => part.length >= 16);
1335
+ if (!proofParts.length) {
1336
+ return true;
1337
+ }
1338
+ const assertionText = normalizeMatchText([
1339
+ assertion.assertion,
1340
+ assertion.workflow,
1341
+ assertion.action,
1342
+ assertion.expected,
1343
+ assertion.observed,
1344
+ assertion.dataProof,
1345
+ assertion.message
1346
+ ].filter(Boolean).join(' '));
1347
+ if (proofParts.some((part) => assertionText.includes(part))) {
1348
+ return true;
1349
+ }
1350
+ const proofTokens = importantTokens([
1351
+ requiredBusinessAssertion,
1352
+ proofPlan.after,
1353
+ proofPlan.data_assertion || proofPlan.dataAssertion
1354
+ ].filter(Boolean).join(' '));
1355
+ const assertionTokens = importantTokens(assertionText);
1356
+ if (!proofTokens.size) {
1357
+ return true;
1358
+ }
1359
+ let overlap = 0;
1360
+ for (const token of proofTokens) {
1361
+ if (assertionTokens.has(token)) {
1362
+ overlap += 1;
1363
+ }
1364
+ }
1365
+ return overlap >= Math.min(4, Math.max(3, Math.ceil(proofTokens.size * 0.45)));
1366
+ }
1367
+
1368
+ function supportDiagnosisProofGateResult(diagnosisGate: Record<string, any>, now?: Date | string): AIRunGateResult {
1369
+ const proofPlan = diagnosisGate.proof_plan || diagnosisGate.proofPlan || {};
1370
+ return {
1371
+ key: 'support_diagnosis_business_proof',
1372
+ label: 'Diagnosis proof plan',
1373
+ status: 'blocked',
1374
+ reason: 'Support business assertion did not map to the diagnosis proof_plan. Route proof or generic workflow proof cannot accept the ticket.',
1375
+ evidenceRefs: [],
1376
+ recordedAt: isoNow(now),
1377
+ metadata: {
1378
+ issueClass: cleanText(diagnosisGate.issue_class || diagnosisGate.issueClass, 120),
1379
+ requiredBusinessAssertion: cleanText(proofPlan.business_assertion || proofPlan.businessAssertion, 1000),
1380
+ requiredAfter: cleanText(proofPlan.after, 1000),
1381
+ requiredDataAssertion: cleanText(proofPlan.data_assertion || proofPlan.dataAssertion, 1000)
1382
+ }
1383
+ };
1384
+ }
1385
+
1386
+ const SUPPORT_ROOT_CAUSE_ENTRY_REQUIRED_FIELDS = [
1387
+ 'issue_case',
1388
+ 'issue_class',
1389
+ 'accepted_hypothesis',
1390
+ 'rejected_alternatives',
1391
+ 'failing_path',
1392
+ 'owner_files',
1393
+ 'proof_plan',
1394
+ 'evidence'
1395
+ ];
1396
+
1397
+ const SUPPORT_ROOT_CAUSE_ENTRY_ISSUE_CLASSES = [
1398
+ 'no_op_submit',
1399
+ 'missing_wrong_data',
1400
+ 'filter_query_mismatch',
1401
+ 'invoice_pdf_export',
1402
+ 'upload_import',
1403
+ 'route_auth_hydration',
1404
+ 'slow_query_performance'
1405
+ ];
1406
+
1407
+ function supportRootCauseEntryContractGate(contract: Record<string, any>, now?: Date | string): AIRunGateResult | undefined {
1408
+ if (!contract || !Object.keys(contract).length) {
1409
+ return undefined;
1410
+ }
1411
+ const requiredOutput = plainObject(contract.required_output || contract.requiredOutput);
1412
+ const ownerPolicy = plainObject(contract.owner_file_policy || contract.ownerFilePolicy);
1413
+ const businessProofPolicy = plainObject(contract.business_proof_policy || contract.businessProofPolicy);
1414
+ const failurePolicy = plainObject(contract.failure_policy || contract.failurePolicy);
1415
+ const probes = asArray<Record<string, any>>(contract.issue_class_probes || contract.issueClassProbes);
1416
+ const requiredFields = cleanStringList(requiredOutput.required_fields || requiredOutput.requiredFields, 30, 160);
1417
+ const issueClasses = probes.map((probe) => cleanText(probe.issue_class || probe.issueClass, 120)).filter(Boolean);
1418
+ const missingRequiredFields = SUPPORT_ROOT_CAUSE_ENTRY_REQUIRED_FIELDS.filter((field) => !requiredFields.includes(field));
1419
+ const missingIssueClasses = SUPPORT_ROOT_CAUSE_ENTRY_ISSUE_CLASSES.filter((issueClass) => !issueClasses.includes(issueClass));
1420
+ const blockers: string[] = [];
1421
+ if (!cleanText(contract.contract_id || contract.contractId, 160)) {
1422
+ blockers.push('Root-cause entry contract is missing contract_id.');
1423
+ }
1424
+ if (cleanText(requiredOutput.object_key || requiredOutput.objectKey, 120) !== 'support_diagnosis_gate') {
1425
+ blockers.push('Root-cause entry contract must require support_diagnosis_gate output.');
1426
+ }
1427
+ if (missingRequiredFields.length) {
1428
+ blockers.push(`Root-cause entry contract missing required output fields: ${missingRequiredFields.join(', ')}.`);
1429
+ }
1430
+ const ownerFileMax = Number(ownerPolicy.max_files || ownerPolicy.maxFiles || 0);
1431
+ if (!Number.isFinite(ownerFileMax) || ownerFileMax <= 0 || ownerFileMax > 12) {
1432
+ blockers.push('Root-cause entry contract must cap owner_files to a small exact file set.');
1433
+ }
1434
+ if (ownerPolicy.edits_outside_owner_files_require_revised_diagnosis !== true && ownerPolicy.editsOutsideOwnerFilesRequireRevisedDiagnosis !== true) {
1435
+ blockers.push('Root-cause entry contract must require revised diagnosis before edits outside owner_files.');
1436
+ }
1437
+ if (businessProofPolicy.requires_aiqa_business_assertion !== true && businessProofPolicy.requiresAiqaBusinessAssertion !== true) {
1438
+ blockers.push('Root-cause entry contract must require AIQaBusinessAssertion business proof.');
1439
+ }
1440
+ if (businessProofPolicy.route_load_screenshot_scorecard_model_claim_not_acceptance !== true && businessProofPolicy.routeLoadScreenshotScorecardModelClaimNotAcceptance !== true) {
1441
+ blockers.push('Root-cause entry contract must reject route-load/screenshot/scorecard/model-claim acceptance.');
1442
+ }
1443
+ if (failurePolicy.repeated_failure_without_new_evidence_parks_run !== true && failurePolicy.repeatedFailureWithoutNewEvidenceParksRun !== true) {
1444
+ blockers.push('Root-cause entry contract must park repeated failures without new evidence.');
1445
+ }
1446
+ if (missingIssueClasses.length) {
1447
+ blockers.push(`Root-cause entry contract missing issue-class probes: ${missingIssueClasses.join(', ')}.`);
1448
+ }
1449
+ const status: AIRunGateResult['status'] = blockers.length ? 'fail' : 'pass';
1450
+ return {
1451
+ key: 'support_root_cause_entry_contract',
1452
+ label: 'Root-cause entry contract',
1453
+ status,
1454
+ reason: blockers.length
1455
+ ? blockers.join(' ')
1456
+ : 'Support run has a structured root-cause-first entry contract with issue-class probes, owner-file policy, and business-proof policy.',
1457
+ evidenceRefs: cleanStringList(businessProofPolicy.required_artifacts || businessProofPolicy.requiredArtifacts, 20, 500),
1458
+ recordedAt: isoNow(now),
1459
+ metadata: {
1460
+ contractId: cleanText(contract.contract_id || contract.contractId, 160),
1461
+ version: cleanText(contract.version, 120),
1462
+ status: cleanText(contract.status, 120),
1463
+ requiredOutputKey: cleanText(requiredOutput.object_key || requiredOutput.objectKey, 120),
1464
+ requiredFields,
1465
+ missingRequiredFields,
1466
+ ownerFileMax,
1467
+ requiresRevisedDiagnosisForOutOfScopeEdits: ownerPolicy.edits_outside_owner_files_require_revised_diagnosis === true || ownerPolicy.editsOutsideOwnerFilesRequireRevisedDiagnosis === true,
1468
+ requiresBusinessAssertion: businessProofPolicy.requires_aiqa_business_assertion === true || businessProofPolicy.requiresAiqaBusinessAssertion === true,
1469
+ routeOnlyAcceptanceRejected: businessProofPolicy.route_load_screenshot_scorecard_model_claim_not_acceptance === true || businessProofPolicy.routeLoadScreenshotScorecardModelClaimNotAcceptance === true,
1470
+ issueClassProbeCount: issueClasses.length,
1471
+ issueClasses,
1472
+ missingIssueClasses
1473
+ }
1474
+ };
1475
+ }
1476
+
1477
+ function applySupportDiagnosisProofGate(qa: AIQaRun, diagnosisGate: Record<string, any>, now?: Date | string): AIQaRun {
1478
+ const proofPlan = diagnosisGate.proof_plan || diagnosisGate.proofPlan || {};
1479
+ const requiredBusinessAssertion = cleanText(proofPlan.business_assertion || proofPlan.businessAssertion, 1000);
1480
+ const requiredAfter = cleanText(proofPlan.after, 1000);
1481
+ if (!Object.keys(proofPlan).length || (!requiredBusinessAssertion && !requiredAfter)) {
1482
+ return qa;
1483
+ }
1484
+ const passedAssertions = qa.businessAssertions.filter(businessAssertionPassed);
1485
+ if (!passedAssertions.length) {
1486
+ return qa;
1487
+ }
1488
+ if (passedAssertions.some((assertion) => supportAssertionMatchesDiagnosisProof(assertion, proofPlan))) {
1489
+ return qa;
1490
+ }
1491
+ const gate = supportDiagnosisProofGateResult(diagnosisGate, now);
1492
+ return {
1493
+ ...qa,
1494
+ outcome: qa.routeProbes.some((probe) => /^(pass|passed|success|ok|done)$/i.test(cleanText(probe.status, 40)))
1495
+ ? 'route_only_pass'
1496
+ : 'incomplete',
1497
+ gateResults: [
1498
+ ...qa.gateResults.filter((existing) => !(existing.key === 'qa_business_assertion' && existing.status === 'pass')),
1499
+ gate
1500
+ ]
1501
+ };
1502
+ }
1503
+
1504
+ const SUPPORT_PRODUCT_REPAIR_STEP_TYPES = new Set([
1505
+ 'build_repair',
1506
+ 'owner_scoped_repair',
1507
+ 'product_repair',
1508
+ 'code_repair'
1509
+ ]);
1510
+
1511
+ const SUPPORT_PRODUCT_REPAIR_ACTIONS = new Set([
1512
+ 'run_owner_scoped_repair',
1513
+ 'run_targeted_product_repair',
1514
+ 'allow_product_repair'
1515
+ ]);
1516
+
1517
+ function supportSourceEditFiles(value: any): string[] {
1518
+ return cleanStringList(value, 80, 500)
1519
+ .filter((filePath) => {
1520
+ const normalized = filePath.replace(/\\/g, '/').toLowerCase();
1521
+ if (!normalized || /(^|\/)(qa-artifacts|runner-evidence|logs?|tmp|dist|node_modules)(\/|$)/.test(normalized)) {
1522
+ return false;
1523
+ }
1524
+ return /\.(?:ts|tsx|js|jsx|html|scss|css|json|mjs|cjs|vue|svelte|md)$/i.test(normalized);
1525
+ });
1526
+ }
1527
+
1528
+ function supportRepairActivityFromRecord(
1529
+ value: any,
1530
+ source: string,
1531
+ index: number
1532
+ ): { changedFiles: string[]; activityRef?: string } | undefined {
1533
+ const record = plainObject(value);
1534
+ if (!Object.keys(record).length) {
1535
+ return undefined;
1536
+ }
1537
+ const stepType = cleanText(record.stepType || record.step_type || record.type, 120);
1538
+ const action = cleanText(record.action || record.primaryAction || record.primary_action || record.allowedDispatchAction || record.allowed_dispatch_action, 120);
1539
+ const lane = cleanText(record.lane || record.phase || record.category, 120).toLowerCase();
1540
+ const changedFiles = supportSourceEditFiles(
1541
+ record.changedFiles
1542
+ || record.changed_files
1543
+ || record.modifiedFiles
1544
+ || record.modified_files
1545
+ || record.editedFiles
1546
+ || record.edited_files
1547
+ || record.files
1548
+ );
1549
+ const explicitProductRepair = SUPPORT_PRODUCT_REPAIR_STEP_TYPES.has(stepType)
1550
+ || SUPPORT_PRODUCT_REPAIR_ACTIONS.has(action)
1551
+ || (lane === 'build' && changedFiles.length > 0);
1552
+ if (!explicitProductRepair) {
1553
+ return undefined;
1554
+ }
1555
+ const activityRef = cleanText(
1556
+ record.microtaskId
1557
+ || record.microtask_id
1558
+ || record.taskId
1559
+ || record.task_id
1560
+ || record.jobId
1561
+ || record.job_id
1562
+ || record._id
1563
+ || record.id
1564
+ || `${source}:${index}`,
1565
+ 240
1566
+ );
1567
+ return { changedFiles, activityRef };
1568
+ }
1569
+
1570
+ function supportProductRepairEvidence(input: SupportAIRunAdapterInput, evidence: Record<string, any>): {
1571
+ attempted: boolean;
1572
+ changedFiles: string[];
1573
+ activityRefs: string[];
1574
+ activityCount: number;
1575
+ } {
1576
+ const job = input.job || {};
1577
+ const records = [
1578
+ ...asArray<any>(job.supportV5StepHistory),
1579
+ ...asArray<any>(job.support_v5_step_history),
1580
+ ...asArray<any>(job.stepHistory),
1581
+ ...asArray<any>(job.step_history),
1582
+ ...asArray<any>(evidence.supportV5StepHistory),
1583
+ ...asArray<any>(evidence.support_v5_step_history),
1584
+ ...asArray<any>(evidence.stepHistory),
1585
+ ...asArray<any>(evidence.step_history),
1586
+ ...asArray<any>(input.taskEvents),
1587
+ ...asArray<any>(input.aiJobs),
1588
+ ...asArray<any>(input.buildPlans)
1589
+ ];
1590
+ const activityRefs: string[] = [];
1591
+ const changedFiles: string[] = [];
1592
+ let activityCount = 0;
1593
+ records.forEach((record, index) => {
1594
+ const activity = supportRepairActivityFromRecord(record, 'support_repair_activity', index);
1595
+ if (!activity) {
1596
+ return;
1597
+ }
1598
+ activityCount += 1;
1599
+ if (activity.activityRef) {
1600
+ activityRefs.push(activity.activityRef);
1601
+ }
1602
+ changedFiles.push(...activity.changedFiles);
1603
+ });
1604
+ const directChangedFiles = supportSourceEditFiles([
1605
+ ...asArray<any>(job.changedFiles),
1606
+ ...asArray<any>(job.changed_files),
1607
+ ...asArray<any>(job.modifiedFiles),
1608
+ ...asArray<any>(job.modified_files),
1609
+ ...asArray<any>(job.editedFiles),
1610
+ ...asArray<any>(job.edited_files),
1611
+ ...asArray<any>(evidence.changedFiles),
1612
+ ...asArray<any>(evidence.changed_files),
1613
+ ...asArray<any>(evidence.modifiedFiles),
1614
+ ...asArray<any>(evidence.modified_files),
1615
+ ...asArray<any>(evidence.editedFiles),
1616
+ ...asArray<any>(evidence.edited_files),
1617
+ ...asArray<any>(input.commits).flatMap((commit) => asArray<any>(commit.changedFiles || commit.changed_files || commit.files))
1618
+ ]);
1619
+ if (directChangedFiles.length) {
1620
+ activityCount += 1;
1621
+ activityRefs.push('changed_files');
1622
+ changedFiles.push(...directChangedFiles);
1623
+ }
1624
+ const uniqueChangedFiles = Array.from(new Set(changedFiles.map((filePath) => cleanText(filePath, 500)).filter(Boolean))).slice(0, 80);
1625
+ return {
1626
+ attempted: activityCount > 0,
1627
+ changedFiles: uniqueChangedFiles,
1628
+ activityRefs: Array.from(new Set(activityRefs.map((ref) => cleanText(ref, 240)).filter(Boolean))).slice(0, 40),
1629
+ activityCount
1630
+ };
1631
+ }
1632
+
1633
+ function supportDiagnosisBeforeRepairGate(
1634
+ diagnosisGate: Record<string, any>,
1635
+ repairEvidence: ReturnType<typeof supportProductRepairEvidence>,
1636
+ now?: Date | string
1637
+ ): AIRunGateResult | undefined {
1638
+ if (!repairEvidence.attempted) {
1639
+ return undefined;
1640
+ }
1641
+ const validation = validateResolveIOSupportDiagnosisGate(diagnosisGate);
1642
+ const status: AIRunGateResult['status'] = validation.valid ? 'pass' : 'blocked';
1643
+ return {
1644
+ key: 'support_diagnosis_before_repair',
1645
+ label: 'Support diagnosis before repair',
1646
+ status,
1647
+ reason: validation.valid
1648
+ ? 'Support product repair has a valid root-cause diagnosis gate and owner-file scope.'
1649
+ : 'Support product repair is blocked until a valid SupportDiagnosisGate records reproduction/classification, accepted hypothesis, rejected alternatives, owner_files, and before/action/after proof.',
1650
+ evidenceRefs: repairEvidence.changedFiles,
1651
+ recordedAt: isoNow(now),
1652
+ metadata: {
1653
+ repairAttempted: true,
1654
+ activityCount: repairEvidence.activityCount,
1655
+ activityRefs: repairEvidence.activityRefs,
1656
+ changedFiles: repairEvidence.changedFiles,
1657
+ diagnosisValid: validation.valid,
1658
+ diagnosisStatus: validation.status,
1659
+ blockers: validation.blockers,
1660
+ ownerFiles: validation.normalized?.owner_files || [],
1661
+ issueClass: validation.normalized?.issue_class,
1662
+ productRepairAllowed: validation.valid,
1663
+ allowedDispatchAction: validation.valid ? 'run_owner_scoped_repair' : 'run_read_only_diagnosis',
1664
+ requiredEvidence: validation.valid
1665
+ ? ['AIQaBusinessAssertion mapped to diagnosis proof_plan before acceptance']
1666
+ : ['support_diagnosis_gate JSON', 'reproduction or blocked-reproduction reason', 'accepted_hypothesis with evidence', 'owner_files', 'proof_plan before/action/after']
1667
+ }
1668
+ };
1669
+ }
1670
+
1671
+ function supportOwnerFileScopeGate(
1672
+ diagnosisGate: Record<string, any>,
1673
+ repairEvidence: ReturnType<typeof supportProductRepairEvidence>,
1674
+ now?: Date | string
1675
+ ): AIRunGateResult | undefined {
1676
+ if (!repairEvidence.attempted) {
1677
+ return undefined;
1678
+ }
1679
+ const validation = validateResolveIOSupportDiagnosisGate(diagnosisGate);
1680
+ if (!validation.valid || !validation.normalized) {
1681
+ return undefined;
1682
+ }
1683
+ const outsideOwnerFiles = changedFilesOutsideResolveIOSupportDiagnosisOwnerFiles(
1684
+ validation.normalized,
1685
+ repairEvidence.changedFiles,
1686
+ { allowTests: true }
1687
+ );
1688
+ const status: AIRunGateResult['status'] = outsideOwnerFiles.length ? 'blocked' : 'pass';
1689
+ return {
1690
+ key: 'support_owner_file_scope',
1691
+ label: 'Support owner-file scope',
1692
+ status,
1693
+ reason: outsideOwnerFiles.length
1694
+ ? `Support repair changed files outside diagnosis owner_files; revise diagnosis with new evidence before broadening edits: ${outsideOwnerFiles.join(', ')}`
1695
+ : 'Support repair stayed within diagnosis owner_files; test-only changes are allowed as supporting evidence.',
1696
+ evidenceRefs: outsideOwnerFiles.length ? outsideOwnerFiles : repairEvidence.changedFiles,
1697
+ recordedAt: isoNow(now),
1698
+ metadata: {
1699
+ repairAttempted: true,
1700
+ diagnosisValid: true,
1701
+ issueClass: validation.normalized.issue_class,
1702
+ ownerFiles: validation.normalized.owner_files,
1703
+ changedFiles: repairEvidence.changedFiles,
1704
+ outsideOwnerFiles,
1705
+ allowedTestFilesOutsideOwnerScope: true,
1706
+ productRepairAllowed: outsideOwnerFiles.length === 0,
1707
+ allowedDispatchAction: outsideOwnerFiles.length ? 'run_read_only_diagnosis' : 'run_business_proof_qa',
1708
+ requiredEvidence: outsideOwnerFiles.length
1709
+ ? ['revised SupportDiagnosisGate with evidence for each new owner file', 'updated failing_path and proof_plan matching broadened scope']
1710
+ : ['compile/unit proof', 'AIQaBusinessAssertion mapped to diagnosis proof_plan']
1711
+ }
1712
+ };
1713
+ }
1714
+
1715
+ function evidenceObject(...values: any[]): Record<string, any> {
1716
+ for (const value of values) {
1717
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
1718
+ return value;
1719
+ }
1720
+ }
1721
+ return {};
1722
+ }
1723
+
1724
+ function mergeEvidenceRecords(...values: any[]): Record<string, any> {
1725
+ const merged: Record<string, any> = {};
1726
+ for (const value of values) {
1727
+ const source = plainObject(value);
1728
+ for (const [key, entry] of Object.entries(source)) {
1729
+ if (entry === undefined || entry === null || entry === '') {
1730
+ continue;
1731
+ }
1732
+ if (Array.isArray(entry)) {
1733
+ merged[key] = [...asArray(merged[key]), ...entry];
1734
+ continue;
1735
+ }
1736
+ if (plainObject(entry) === entry && plainObject(merged[key]) === merged[key]) {
1737
+ merged[key] = { ...merged[key], ...entry };
1738
+ continue;
1739
+ }
1740
+ merged[key] = entry;
1741
+ }
1742
+ }
1743
+ return merged;
1744
+ }
1745
+
1746
+ function buildSupportQaEvidence(input: SupportAIRunAdapterInput): Record<string, any> {
1747
+ const job = input.job || {};
1748
+ const ticket = input.ticket || {};
1749
+ const qaCursor = plainObject(job.supportQaValidationCursor || job.support_qa_validation_cursor);
1750
+ const autonomousDecision = plainObject(job.supportV5AutonomousDecision || job.support_v5_autonomous_decision);
1751
+ const ticketAutomation = plainObject(ticket.automation);
1752
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
1753
+ const preflightGate = supportPreflightGateObject(ticket, job, {
1754
+ ...plainObject(input.qaEvidence),
1755
+ supportV5AutonomousDecision: autonomousDecision
1756
+ });
1757
+ const businessProofReadiness = plainObject(
1758
+ autonomousDecision.businessProofReadiness
1759
+ || autonomousDecision.business_proof_readiness
1760
+ || job.businessProofReadiness
1761
+ || job.business_proof_readiness
1762
+ );
1763
+ const customerReplyPolicy = plainObject(
1764
+ autonomousDecision.customerReplyPolicy
1765
+ || autonomousDecision.customer_reply_policy
1766
+ || job.customerReplyPolicy
1767
+ || job.customer_reply_policy
1768
+ );
1769
+ const customerReplyReadiness = plainObject(
1770
+ autonomousDecision.customerReplyReadiness
1771
+ || autonomousDecision.customer_reply_readiness
1772
+ || customerReplyPolicy.readinessContract
1773
+ || customerReplyPolicy.readiness_contract
1774
+ || job.customerReplyReadiness
1775
+ || job.customer_reply_readiness
1776
+ );
1777
+ const managerRecoveryExecutionProof = plainObject(
1778
+ job.managerRecoveryExecutionProof
1779
+ || job.manager_recovery_execution_proof
1780
+ || job.supportManagerRecoveryExecutionProof
1781
+ || job.support_manager_recovery_execution_proof
1782
+ || autonomousDecision.recoveryExecutionProof
1783
+ || autonomousDecision.recovery_execution_proof
1784
+ || ticketManager.last_recovery_execution_proof
1785
+ || ticketManager.recovery_execution_proof
1786
+ );
1787
+ const humanReviewPacket = plainObject(
1788
+ autonomousDecision.humanReviewPacket
1789
+ || autonomousDecision.human_review_packet
1790
+ || customerReplyPolicy.humanReviewPacket
1791
+ || customerReplyPolicy.human_review_packet
1792
+ || job.humanReviewPacket
1793
+ || job.human_review_packet
1794
+ );
1795
+ const evidence = mergeEvidenceRecords(
1796
+ qaCursor,
1797
+ job.qaEvidence,
1798
+ job.qa_evidence,
1799
+ ticket.qaEvidence,
1800
+ ticket.qa_evidence,
1801
+ input.qaEvidence
1802
+ );
1803
+ evidence.supportQaAssertions = [
1804
+ ...asArray(job.supportQaAssertions),
1805
+ ...asArray(job.support_qa_assertions),
1806
+ ...asArray(evidence.supportQaAssertions)
1807
+ ];
1808
+ evidence.aiQaBusinessAssertions = [
1809
+ ...asArray(job.aiQaBusinessAssertions),
1810
+ ...asArray(job.ai_qa_business_assertions),
1811
+ ...asArray(evidence.aiQaBusinessAssertions)
1812
+ ];
1813
+ evidence.runnerEvidenceArtifacts = [
1814
+ ...asArray(job.runnerEvidenceArtifacts),
1815
+ ...asArray(job.runner_evidence_artifacts),
1816
+ ...asArray(evidence.runnerEvidenceArtifacts)
1817
+ ];
1818
+ evidence.supportQaArtifacts = [
1819
+ ...asArray(job.supportQaArtifacts),
1820
+ ...asArray(job.support_qa_artifacts),
1821
+ ...asArray(job.qa_artifacts),
1822
+ ...asArray(evidence.supportQaArtifacts)
1823
+ ];
1824
+ if (Object.keys(businessProofReadiness).length) {
1825
+ evidence.businessProofReadiness = businessProofReadiness;
1826
+ }
1827
+ if (Object.keys(customerReplyPolicy).length) {
1828
+ evidence.customerReplyPolicy = {
1829
+ ...customerReplyPolicy,
1830
+ ...(Object.keys(humanReviewPacket).length ? { humanReviewPacket } : {})
1831
+ };
1832
+ }
1833
+ else if (Object.keys(humanReviewPacket).length) {
1834
+ evidence.customerReplyPolicy = { humanReviewPacket };
1835
+ }
1836
+ if (Object.keys(customerReplyReadiness).length) {
1837
+ evidence.customerReplyReadiness = customerReplyReadiness;
1838
+ }
1839
+ if (Object.keys(managerRecoveryExecutionProof).length) {
1840
+ evidence.managerRecoveryExecutionProof = managerRecoveryExecutionProof;
1841
+ }
1842
+ if (Object.keys(preflightGate).length) {
1843
+ evidence.supportV5PreflightGate = preflightGate;
1844
+ applySupportPreflightGateToQaEvidence(evidence, preflightGate);
1845
+ }
1846
+ return evidence;
1847
+ }
1848
+
1849
+ function supportPreflightGateObject(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
1850
+ const autonomousDecision = plainObject(
1851
+ job.supportV5AutonomousDecision
1852
+ || job.support_v5_autonomous_decision
1853
+ || evidence.supportV5AutonomousDecision
1854
+ || evidence.support_v5_autonomous_decision
1855
+ || ticket.supportV5AutonomousDecision
1856
+ || ticket.support_v5_autonomous_decision
1857
+ );
1858
+ const ticketAutomation = plainObject(ticket.automation);
1859
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
1860
+ return evidenceObject(
1861
+ job.supportV5PreflightGate,
1862
+ job.support_v5_preflight_gate,
1863
+ job.preflightGate,
1864
+ job.preflight_gate,
1865
+ evidence.supportV5PreflightGate,
1866
+ evidence.support_v5_preflight_gate,
1867
+ evidence.preflightGate,
1868
+ evidence.preflight_gate,
1869
+ autonomousDecision.preflightGate,
1870
+ autonomousDecision.preflight_gate,
1871
+ autonomousDecision.rootCauseReadiness?.preflightGate,
1872
+ autonomousDecision.root_cause_readiness?.preflight_gate,
1873
+ ticket.supportV5PreflightGate,
1874
+ ticket.support_v5_preflight_gate,
1875
+ ticketManager.supportV5PreflightGate,
1876
+ ticketManager.support_v5_preflight_gate,
1877
+ ticketManager.preflightGate,
1878
+ ticketManager.preflight_gate
1879
+ );
1880
+ }
1881
+
1882
+ function supportPreflightStatus(value: any): string {
1883
+ return cleanText(value, 120).toLowerCase();
1884
+ }
1885
+
1886
+ function supportPreflightFailedChecks(preflightGate: Record<string, any>): Record<string, any>[] {
1887
+ const failed = [
1888
+ ...asArray<Record<string, any>>(preflightGate.failedChecks || preflightGate.failed_checks),
1889
+ ...asArray<Record<string, any>>(preflightGate.checks).filter((check) => {
1890
+ const status = supportPreflightStatus(check.status || check.state || check.outcome);
1891
+ return /fail|failed|blocked|missing|error/.test(status);
1892
+ })
1893
+ ];
1894
+ const seen = new Set<string>();
1895
+ return failed.filter((check) => {
1896
+ const key = `${cleanText(check.name || check.check || check.key, 160)}:${cleanText(check.summary || check.message || check.error || check.reason, 500)}`;
1897
+ if (seen.has(key)) {
1898
+ return false;
1899
+ }
1900
+ seen.add(key);
1901
+ return true;
1902
+ });
1903
+ }
1904
+
1905
+ function supportPreflightArtifactPaths(preflightGate: Record<string, any>, checks: Record<string, any>[]): string[] {
1906
+ return Array.from(new Set([
1907
+ ...cleanStringList(preflightGate.artifactPaths || preflightGate.artifact_paths, 60, 500),
1908
+ ...checks.flatMap((check) => cleanStringList(check.artifactPaths || check.artifact_paths || check.artifacts, 20, 500)),
1909
+ ...checks.map((check) => cleanText(check.artifactPath || check.artifact_path || check.path || check.file, 500)).filter(Boolean)
1910
+ ])).slice(0, 80);
1911
+ }
1912
+
1913
+ function supportPreflightCheckMessage(check: Record<string, any>): string {
1914
+ return cleanText(check.summary || check.message || check.error || check.reason || check.status, 1000);
1915
+ }
1916
+
1917
+ function applySupportPreflightGateToQaEvidence(evidence: Record<string, any>, preflightGate: Record<string, any>): void {
1918
+ const status = supportPreflightStatus(preflightGate.status);
1919
+ const failureClass = supportPreflightStatus(preflightGate.failureClass || preflightGate.failure_class);
1920
+ const failedChecks = supportPreflightFailedChecks(preflightGate);
1921
+ const artifactPaths = supportPreflightArtifactPaths(preflightGate, failedChecks);
1922
+ const compileResult = plainObject(preflightGate.compileResult || preflightGate.compile_result);
1923
+ const compileFailed = status === 'compile_failed'
1924
+ || failureClass === 'compile'
1925
+ || cleanText(compileResult.failureClass || compileResult.failure_class, 120).toLowerCase() === 'compile'
1926
+ || failedChecks.some((check) => {
1927
+ const name = supportPreflightStatus(check.name || check.check || check.key);
1928
+ const checkClass = supportPreflightStatus(check.failureClass || check.failure_class);
1929
+ return name === 'compile' || checkClass === 'compile';
1930
+ });
1931
+ if (compileFailed) {
1932
+ const compileCheck = Object.keys(compileResult).length ? compileResult : failedChecks[0] || {};
1933
+ evidence.compile = {
1934
+ ...plainObject(evidence.compile || evidence.build || evidence.buildResult || evidence.build_result),
1935
+ status: cleanText(compileCheck.status || preflightGate.status || 'fail', 80) || 'fail',
1936
+ command: cleanText(compileCheck.command || preflightGate.command || preflightGate.nextCommand || preflightGate.next_command, 500),
1937
+ artifactPath: artifactPaths[0] || cleanText(compileCheck.artifactPath || compileCheck.artifact_path || compileCheck.path, 500),
1938
+ message: supportPreflightCheckMessage(compileCheck) || cleanText(preflightGate.reason || preflightGate.blockers?.[0] || 'Support compile preflight failed before browser QA.', 1000),
1939
+ metadata: {
1940
+ source: 'supportV5PreflightGate',
1941
+ preflightStatus: status,
1942
+ preflightEvidenceHash: cleanText(preflightGate.evidenceHash || preflightGate.evidence_hash, 200),
1943
+ nextCommand: cleanText(preflightGate.nextCommand || preflightGate.next_command, 180)
1944
+ }
1945
+ };
1946
+ return;
1947
+ }
1948
+ const blocksRepair = preflightGate.blocksProductRepair === true || preflightGate.blocks_product_repair === true || /infra_failed|blocked|missing/.test(status);
1949
+ const checks = failedChecks.length
1950
+ ? failedChecks
1951
+ : Object.keys(preflightGate).length && (blocksRepair || status === 'passed')
1952
+ ? [{
1953
+ name: 'support_preflight',
1954
+ status: status === 'passed' ? 'pass' : 'blocked',
1955
+ summary: cleanStringList(preflightGate.blockers, 4, 500).join('; ') || cleanText(preflightGate.reason || preflightGate.status, 1000),
1956
+ artifactPaths
1957
+ }]
1958
+ : [];
1959
+ if (!checks.length) {
1960
+ return;
1961
+ }
1962
+ evidence.infraChecks = [
1963
+ ...asArray(evidence.infraChecks),
1964
+ ...checks.map((check) => ({
1965
+ name: cleanText(check.name || check.check || check.key, 160) || 'support_preflight',
1966
+ status: cleanText(check.status || check.state || check.outcome || (status === 'passed' ? 'pass' : 'blocked'), 80),
1967
+ message: supportPreflightCheckMessage(check) || cleanStringList(preflightGate.blockers, 3, 500).join('; '),
1968
+ path: cleanText(check.path || check.artifactPath || check.artifact_path, 500) || artifactPaths[0],
1969
+ metadata: {
1970
+ source: 'supportV5PreflightGate',
1971
+ preflightStatus: status,
1972
+ preflightFailureClass: failureClass,
1973
+ preflightEvidenceHash: cleanText(preflightGate.evidenceHash || preflightGate.evidence_hash, 200),
1974
+ nextCommand: cleanText(preflightGate.nextCommand || preflightGate.next_command, 180)
1975
+ }
1976
+ }))
1977
+ ];
1978
+ }
1979
+
1980
+ function supportPreflightGateResult(
1981
+ preflightGate: Record<string, any>,
1982
+ now?: Date | string
1983
+ ): AIRunGateResult | undefined {
1984
+ if (!preflightGate || !Object.keys(preflightGate).length) {
1985
+ return undefined;
1986
+ }
1987
+ const statusText = supportPreflightStatus(preflightGate.status);
1988
+ const ready = preflightGate.ready === true;
1989
+ const blocksProductRepair = preflightGate.blocksProductRepair === true || preflightGate.blocks_product_repair === true;
1990
+ const failedChecks = supportPreflightFailedChecks(preflightGate);
1991
+ const artifactPaths = supportPreflightArtifactPaths(preflightGate, failedChecks);
1992
+ const gateStatus: AIRunGateResult['status'] = ready || statusText === 'passed'
1993
+ ? 'pass'
1994
+ : /compile_failed|infra_failed|fail|failed/.test(statusText)
1995
+ ? 'fail'
1996
+ : blocksProductRepair || /missing|blocked/.test(statusText)
1997
+ ? 'blocked'
1998
+ : 'warn';
1999
+ const blockers = cleanStringList(preflightGate.blockers, 20, 500);
2000
+ return {
2001
+ key: 'support_preflight_gate',
2002
+ label: 'Support preflight gate',
2003
+ status: gateStatus,
2004
+ reason: gateStatus === 'pass'
2005
+ ? 'Support deterministic preflight passed before product-code repair.'
2006
+ : cleanText(blockers.join('; ') || preflightGate.reason || `Support deterministic preflight is ${statusText || 'not ready'} before product-code repair.`, 1200),
2007
+ evidenceRefs: artifactPaths,
2008
+ recordedAt: isoNow(now || preflightGate.recordedAt || preflightGate.recorded_at),
2009
+ metadata: {
2010
+ ready,
2011
+ required: preflightGate.required === true,
2012
+ status: statusText,
2013
+ failureClass: cleanText(preflightGate.failureClass || preflightGate.failure_class, 120),
2014
+ nextCommand: cleanText(preflightGate.nextCommand || preflightGate.next_command, 180),
2015
+ blocksProductRepair,
2016
+ blocksModelRepair: preflightGate.blocksModelRepair === true || preflightGate.blocks_model_repair === true,
2017
+ failedChecks: failedChecks.map((check) => cleanText(check.name || check.check || check.key, 160)).filter(Boolean),
2018
+ requiredEvidence: cleanStringList(preflightGate.requiredEvidence || preflightGate.required_evidence, 20, 500),
2019
+ evidenceHash: cleanText(preflightGate.evidenceHash || preflightGate.evidence_hash, 200)
2020
+ }
2021
+ };
2022
+ }
2023
+
2024
+ function supportBusinessProofReadinessGate(
2025
+ readiness: Record<string, any>,
2026
+ now?: Date | string
2027
+ ): AIRunGateResult | undefined {
2028
+ if (!readiness || !Object.keys(readiness).length) {
2029
+ return undefined;
2030
+ }
2031
+ const ready = readiness.ready === true;
2032
+ const readinessStatus = cleanText(readiness.status, 120).toLowerCase();
2033
+ const blockers = cleanStringList(readiness.blockers, 20, 500);
2034
+ const artifactPaths = cleanStringList(readiness.artifactPaths || readiness.artifact_paths, 40, 500);
2035
+ const proofFingerprint = cleanText(readiness.proofFingerprint || readiness.proof_fingerprint, 160);
2036
+ const artifactFingerprint = cleanText(readiness.artifactFingerprint || readiness.artifact_fingerprint, 160);
2037
+ const proofFreshness = cleanText(readiness.proofFreshness || readiness.proof_freshness, 120);
2038
+ const status: AIRunGateResult['status'] = ready
2039
+ ? 'pass'
2040
+ : /fail|failed/.test(readinessStatus)
2041
+ ? 'fail'
2042
+ : /missing|route_only|blocked|weak|stale|incomplete/.test(readinessStatus)
2043
+ ? 'blocked'
2044
+ : 'warn';
2045
+ return {
2046
+ key: 'support_business_proof_readiness',
2047
+ label: 'Support business proof readiness',
2048
+ status,
2049
+ reason: cleanText(readiness.reason || blockers.join('; ') || readinessStatus || 'Support business proof readiness recorded.', 1200),
2050
+ evidenceRefs: artifactPaths,
2051
+ recordedAt: isoNow(now || readiness.recordedAt || readiness.recorded_at),
2052
+ metadata: {
2053
+ ready,
2054
+ status: readinessStatus,
2055
+ blockers,
2056
+ requiredEvidence: cleanStringList(readiness.requiredEvidence || readiness.required_evidence, 20, 500),
2057
+ proofFingerprint,
2058
+ artifactFingerprint,
2059
+ proofFreshness
2060
+ }
2061
+ };
2062
+ }
2063
+
2064
+ function supportCustomerReplyPolicyGate(
2065
+ policy: Record<string, any>,
2066
+ now?: Date | string
2067
+ ): AIRunGateResult | undefined {
2068
+ if (!policy || !Object.keys(policy).length) {
2069
+ return undefined;
2070
+ }
2071
+ const action = cleanText(policy.action, 120);
2072
+ const safety = cleanText(policy.safety, 120);
2073
+ const reason = cleanText(policy.reason, 1200);
2074
+ const canDraft = policy.canDraftCustomerReply === true || policy.can_draft_customer_reply === true;
2075
+ const canSend = policy.canSendCustomerReply === true || policy.can_send_customer_reply === true;
2076
+ const reviewPacket = plainObject(policy.humanReviewPacket || policy.human_review_packet);
2077
+ const draftBasis = plainObject(policy.draftBasis || policy.draft_basis);
2078
+ const clarificationValidation = validateResolveIOSupportClarificationContract(policy.clarificationContract || policy.clarification_contract || {});
2079
+ const clarificationContract = clarificationValidation.normalized;
2080
+ const reviewType = cleanText(reviewPacket.reviewType || reviewPacket.review_type, 160);
2081
+ const evidenceRefs = cleanStringList(reviewPacket.evidenceRefs || reviewPacket.evidence_refs, 40, 500);
2082
+ const blockers = cleanStringList(policy.blockers || reviewPacket.blockers, 20, 500);
2083
+ const gateStatus: AIRunGateResult['status'] = canSend
2084
+ ? 'blocked'
2085
+ : action === 'draft_resolution_reply' && canDraft
2086
+ ? 'warn'
2087
+ : action === 'ask_clarification' && canDraft
2088
+ ? (clarificationValidation.valid ? 'warn' : 'blocked')
2089
+ : 'blocked';
2090
+ const mergedBlockers = Array.from(new Set([
2091
+ ...blockers,
2092
+ ...(action === 'ask_clarification' ? clarificationValidation.blockers : [])
2093
+ ]));
2094
+ return {
2095
+ key: 'support_customer_reply_policy',
2096
+ label: 'Support customer reply policy',
2097
+ status: gateStatus,
2098
+ reason: reason || mergedBlockers.join('; ') || action || 'Support customer reply policy recorded.',
2099
+ evidenceRefs,
2100
+ recordedAt: isoNow(now || policy.recordedAt || policy.recorded_at || reviewPacket.createdAt || reviewPacket.created_at),
2101
+ metadata: {
2102
+ action,
2103
+ safety,
2104
+ canDraftCustomerReply: canDraft,
2105
+ canSendCustomerReply: canSend,
2106
+ confidenceLevel: cleanText(policy.confidenceLevel || policy.confidence_level, 120),
2107
+ issueClass: cleanText(draftBasis.issueClass || draftBasis.issue_class, 120),
2108
+ issueClassProbePlanId: cleanText(draftBasis.issueClassProbePlanId || draftBasis.issue_class_probe_plan_id, 180),
2109
+ issueClassProbePlanStatus: cleanText(draftBasis.issueClassProbePlanStatus || draftBasis.issue_class_probe_plan_status, 120),
2110
+ reviewType,
2111
+ primaryAction: cleanText(reviewPacket.primaryAction || reviewPacket.primary_action, 160),
2112
+ requiresHumanApproval: reviewPacket.requiresHumanApproval !== false && reviewPacket.requires_human_approval !== false,
2113
+ questionPresent: !!cleanText(policy.clarificationQuestion || policy.clarification_question || reviewPacket.question, 1000),
2114
+ clarificationContractStatus: cleanText(clarificationContract.status, 120),
2115
+ clarificationMissingField: cleanText(clarificationContract.missingField, 120),
2116
+ clarificationOneQuestionOnly: clarificationContract.oneQuestionOnly === true,
2117
+ clarificationParksTicket: clarificationContract.parksTicketUntilCustomerReply === true,
2118
+ blockers: mergedBlockers
2119
+ }
2120
+ };
2121
+ }
2122
+
2123
+ function supportCustomerReplyReadinessGate(
2124
+ contract: Record<string, any>,
2125
+ now?: Date | string
2126
+ ): AIRunGateResult | undefined {
2127
+ if (!contract || !Object.keys(contract).length) {
2128
+ return undefined;
2129
+ }
2130
+ const validation = validateResolveIOSupportCustomerReplyReadinessContract(contract);
2131
+ const normalized = validation.normalized;
2132
+ const status = cleanText(normalized.status, 120);
2133
+ const gateStatus: AIRunGateResult['status'] = normalized.canSendCustomerReply
2134
+ ? 'blocked'
2135
+ : validation.valid && (status === 'draft_ready' || status === 'clarification_required')
2136
+ ? 'warn'
2137
+ : 'blocked';
2138
+ return {
2139
+ key: 'support_customer_reply_readiness',
2140
+ label: 'Support customer reply readiness',
2141
+ status: gateStatus,
2142
+ reason: normalized.reason || validation.blockers.join('; ') || status || 'Support customer reply readiness recorded.',
2143
+ evidenceRefs: normalized.evidenceRefs,
2144
+ recordedAt: isoNow(now || normalized.createdAt),
2145
+ metadata: {
2146
+ contractId: normalized.contractId,
2147
+ status,
2148
+ policyAction: normalized.policyAction,
2149
+ reviewType: normalized.reviewType,
2150
+ primaryCommand: normalized.primaryCommand,
2151
+ canDraftCustomerReply: normalized.canDraftCustomerReply,
2152
+ canSendCustomerReply: normalized.canSendCustomerReply,
2153
+ requiresHumanApproval: normalized.requiresHumanApproval,
2154
+ proofBacked: normalized.proofBacked,
2155
+ businessProofReady: normalized.businessProofReady,
2156
+ releaseReady: normalized.releaseReady,
2157
+ diagnosisReady: normalized.diagnosisReady,
2158
+ issueClassProbePlanReady: normalized.issueClassProbePlanReady,
2159
+ clarificationRequired: normalized.clarificationRequired,
2160
+ blockers: Array.from(new Set([
2161
+ ...normalized.blockers,
2162
+ ...validation.blockers
2163
+ ])),
2164
+ requiredEvidence: normalized.requiredEvidence,
2165
+ nextCommands: normalized.nextCommands,
2166
+ forbiddenActions: normalized.forbiddenActions
2167
+ }
2168
+ };
2169
+ }
2170
+
2171
+ function supportNextActionContractObject(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2172
+ const ticketAutomation = plainObject(ticket.automation);
2173
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
2174
+ const autonomousDecision = plainObject(
2175
+ job.supportV5AutonomousDecision
2176
+ || job.support_v5_autonomous_decision
2177
+ || evidence.supportV5AutonomousDecision
2178
+ || evidence.support_v5_autonomous_decision
2179
+ || ticket.supportV5AutonomousDecision
2180
+ || ticket.support_v5_autonomous_decision
2181
+ );
2182
+ return evidenceObject(
2183
+ autonomousDecision.nextActionContract,
2184
+ autonomousDecision.next_action_contract,
2185
+ job.supportV5NextActionContract,
2186
+ job.support_v5_next_action_contract,
2187
+ job.nextActionContract,
2188
+ job.next_action_contract,
2189
+ evidence.supportV5NextActionContract,
2190
+ evidence.support_v5_next_action_contract,
2191
+ evidence.nextActionContract,
2192
+ evidence.next_action_contract,
2193
+ ticket.supportV5NextActionContract,
2194
+ ticket.support_v5_next_action_contract,
2195
+ ticket.nextActionContract,
2196
+ ticket.next_action_contract,
2197
+ ticketManager.nextActionContract,
2198
+ ticketManager.next_action_contract
2199
+ );
2200
+ }
2201
+
2202
+ function supportNextActionContractRequired(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): boolean {
2203
+ const ticketAutomation = plainObject(ticket.automation);
2204
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
2205
+ const workflowMarkers = [
2206
+ job.supportWorkflowMode,
2207
+ job.workflowMode,
2208
+ job.workflow_mode,
2209
+ job.supportWorkflowVersion,
2210
+ job.support_workflow_version,
2211
+ ticket.supportWorkflowMode,
2212
+ ticket.workflowMode,
2213
+ ticket.workflow_mode
2214
+ ].map((value) => cleanText(value, 80).toLowerCase());
2215
+ return workflowMarkers.some((value) => value === 'support_v5' || value === 'v5')
2216
+ || !!job.supportV5SupervisorState
2217
+ || !!job.supportV5MicrotaskLedger
2218
+ || !!job.supportV5StepHistory
2219
+ || !!job.supportV5DiagnosisGate
2220
+ || !!job.supportV5DiagnosisEvidencePack
2221
+ || !!job.support_v5_supervisor_state
2222
+ || !!job.support_v5_microtask_ledger
2223
+ || !!job.support_v5_step_history
2224
+ || !!job.support_v5_diagnosis_gate
2225
+ || !!job.support_v5_diagnosis_evidence_pack
2226
+ || !!job.supportRootCauseEntryContract
2227
+ || !!job.support_root_cause_entry_contract
2228
+ || !!evidence.supportV5AutonomousDecision
2229
+ || !!evidence.support_v5_autonomous_decision
2230
+ || !!evidence.rootCauseReadiness
2231
+ || !!evidence.root_cause_readiness
2232
+ || !!ticketManager.root_cause_entry_contract
2233
+ || !!ticketManager.rootCauseEntryContract
2234
+ || !!ticketManager.last_watchdog_decision;
2235
+ }
2236
+
2237
+ function supportNextActionContractMetadata(contract: Record<string, any>): Record<string, any> {
2238
+ const decisionBasis = plainObject(contract.decisionBasis || contract.decision_basis);
2239
+ const costRisk = plainObject(contract.costRisk || contract.cost_risk);
2240
+ const source = {
2241
+ contractId: cleanText(contract.contractId || contract.contract_id, 200),
2242
+ action: cleanText(contract.action, 160),
2243
+ label: cleanText(contract.label, 300),
2244
+ primaryCommand: cleanText(contract.primaryCommand || contract.primary_command, 240),
2245
+ lane: cleanText(contract.lane, 120),
2246
+ stepType: cleanText(contract.stepType || contract.step_type, 120),
2247
+ safeToAutoRun: booleanFlag(contract.safeToAutoRun ?? contract.safe_to_auto_run),
2248
+ requiresHumanApproval: booleanFlag(contract.requiresHumanApproval ?? contract.requires_human_approval),
2249
+ canRunWithoutCodexMonitor: booleanFlag(contract.canRunWithoutCodexMonitor ?? contract.can_run_without_codex_monitor),
2250
+ codexFallbackRequired: booleanFlag(contract.codexFallbackRequired ?? contract.codex_fallback_required),
2251
+ codexFallbackReason: cleanText(contract.codexFallbackReason || contract.codex_fallback_reason, 1000),
2252
+ rootCauseFirstSatisfied: booleanFlag(contract.rootCauseFirstSatisfied ?? contract.root_cause_first_satisfied),
2253
+ preconditions: cleanStringList(contract.preconditions, 40, 500),
2254
+ expectedStateTransition: cleanText(contract.expectedStateTransition || contract.expected_state_transition, 1000),
2255
+ successEvidence: cleanStringList(contract.successEvidence || contract.success_evidence, 40, 500),
2256
+ stopConditions: cleanStringList(contract.stopConditions || contract.stop_conditions, 40, 500),
2257
+ forbiddenActions: cleanStringList(contract.forbiddenActions || contract.forbidden_actions, 40, 500),
2258
+ ownerFiles: cleanStringList(contract.ownerFiles || contract.owner_files || decisionBasis.ownerFiles || decisionBasis.owner_files, 40, 500),
2259
+ blockers: cleanStringList(contract.blockers, 40, 500),
2260
+ nextCommands: cleanStringList(contract.nextCommands || contract.next_commands, 40, 500),
2261
+ costRisk: {
2262
+ level: cleanText(costRisk.level || contract.costRiskLevel || contract.cost_risk_level, 80),
2263
+ estimatedUsd: numberValue(costRisk.estimatedUsd ?? costRisk.estimated_usd ?? contract.estimatedUsd ?? contract.estimated_usd),
2264
+ reason: cleanText(costRisk.reason, 800)
2265
+ },
2266
+ diagnosisValid: booleanFlag(decisionBasis.diagnosisValid ?? decisionBasis.diagnosis_valid),
2267
+ ownerFilesReady: booleanFlag(decisionBasis.ownerFilesReady ?? decisionBasis.owner_files_ready),
2268
+ proofPlanReady: booleanFlag(decisionBasis.proofPlanReady ?? decisionBasis.proof_plan_ready),
2269
+ businessProofReady: booleanFlag(decisionBasis.businessProofReady ?? decisionBasis.business_proof_ready),
2270
+ evidenceFreshnessStatus: cleanText(decisionBasis.evidenceFreshnessStatus || decisionBasis.evidence_freshness_status, 120),
2271
+ evidenceStrength: cleanText(decisionBasis.evidenceStrength || decisionBasis.evidence_strength, 120),
2272
+ failureClass: cleanText(decisionBasis.failureClass || decisionBasis.failure_class, 120),
2273
+ blockerFingerprint: cleanText(decisionBasis.blockerFingerprint || decisionBasis.blocker_fingerprint, 200),
2274
+ evidenceHash: cleanText(decisionBasis.evidenceHash || decisionBasis.evidence_hash, 200),
2275
+ sameFailureCount: numberValue(decisionBasis.sameFailureCount ?? decisionBasis.same_failure_count),
2276
+ preflightReady: booleanFlag(decisionBasis.preflightReady ?? decisionBasis.preflight_ready),
2277
+ preflightStatus: cleanText(decisionBasis.preflightStatus || decisionBasis.preflight_status, 120),
2278
+ preflightFailureClass: cleanText(decisionBasis.preflightFailureClass || decisionBasis.preflight_failure_class, 120),
2279
+ preflightEvidenceHash: cleanText(decisionBasis.preflightEvidenceHash || decisionBasis.preflight_evidence_hash, 200),
2280
+ hotfixCommitRequired: booleanFlag(decisionBasis.hotfixCommitRequired ?? decisionBasis.hotfix_commit_required ?? contract.hotfixCommitRequired ?? contract.hotfix_commit_required),
2281
+ liveHotfixBlockedUntilCommit: booleanFlag(decisionBasis.liveHotfixBlockedUntilCommit ?? decisionBasis.live_hotfix_blocked_until_commit ?? contract.liveHotfixBlockedUntilCommit ?? contract.live_hotfix_blocked_until_commit)
2282
+ };
2283
+ return source;
2284
+ }
2285
+
2286
+ function supportNextActionContractGate(contract: Record<string, any>, now?: Date | string, required = false): AIRunGateResult | undefined {
2287
+ if (!contract || !Object.keys(contract).length) {
2288
+ if (!required) {
2289
+ return undefined;
2290
+ }
2291
+ const blocker = 'Support V5 run is missing a validated next-action contract, so the manager cannot prove one safe primary action without external Codex monitoring.';
2292
+ return {
2293
+ key: 'support_next_action_contract',
2294
+ label: 'Support next-action contract',
2295
+ status: 'blocked',
2296
+ reason: blocker,
2297
+ evidenceRefs: [],
2298
+ recordedAt: isoNow(now),
2299
+ metadata: {
2300
+ required: true,
2301
+ missingContract: true,
2302
+ safeToAutoRun: false,
2303
+ canRunWithoutCodexMonitor: false,
2304
+ codexFallbackRequired: true,
2305
+ codexFallbackReason: 'missing_support_next_action_contract',
2306
+ blockers: [blocker],
2307
+ missingSafetyFlags: ['safeToAutoRun', 'canRunWithoutCodexMonitor', 'codexFallbackRequired']
2308
+ }
2309
+ };
2310
+ }
2311
+ const metadata = supportNextActionContractMetadata(contract);
2312
+ const blockers = cleanStringList(metadata.blockers, 40, 500);
2313
+ if (metadata.safeToAutoRun === false) {
2314
+ blockers.push('Next action contract marks the action unsafe for autonomous run.');
2315
+ }
2316
+ if (metadata.canRunWithoutCodexMonitor === false) {
2317
+ blockers.push('Next action requires Codex fallback/monitoring instead of manager-only execution.');
2318
+ }
2319
+ if (metadata.codexFallbackRequired === true) {
2320
+ blockers.push(metadata.codexFallbackReason || 'Next action contract requires Codex fallback.');
2321
+ }
2322
+ if (metadata.requiresHumanApproval === true) {
2323
+ blockers.push('Next action contract requires human approval.');
2324
+ }
2325
+ if (metadata.liveHotfixBlockedUntilCommit === true) {
2326
+ blockers.push('Live hotfix is blocked until the manager records and pushes the GitHub commit proof.');
2327
+ }
2328
+ const missingSafetyFlags = [
2329
+ metadata.safeToAutoRun === undefined ? 'safeToAutoRun' : '',
2330
+ metadata.canRunWithoutCodexMonitor === undefined ? 'canRunWithoutCodexMonitor' : '',
2331
+ metadata.codexFallbackRequired === undefined ? 'codexFallbackRequired' : ''
2332
+ ].filter(Boolean);
2333
+ const status: AIRunGateResult['status'] = blockers.length
2334
+ ? 'blocked'
2335
+ : missingSafetyFlags.length
2336
+ ? 'warn'
2337
+ : 'pass';
2338
+ const actionLabel = metadata.label || metadata.action || metadata.primaryCommand || 'support next action';
2339
+ return {
2340
+ key: 'support_next_action_contract',
2341
+ label: 'Support next-action contract',
2342
+ status,
2343
+ reason: blockers.length
2344
+ ? blockers.join(' ')
2345
+ : missingSafetyFlags.length
2346
+ ? `Support next-action contract is present for ${actionLabel}, but missing explicit safety flags: ${missingSafetyFlags.join(', ')}.`
2347
+ : `Support next-action contract allows ${actionLabel} without Codex monitoring.`,
2348
+ evidenceRefs: cleanStringList([
2349
+ ...metadata.successEvidence,
2350
+ ...metadata.preconditions,
2351
+ ...metadata.stopConditions
2352
+ ], 40, 500),
2353
+ recordedAt: isoNow(now || contract.createdAt || contract.created_at || contract.recordedAt || contract.recorded_at),
2354
+ metadata: {
2355
+ ...metadata,
2356
+ blockers,
2357
+ missingSafetyFlags
2358
+ }
2359
+ };
2360
+ }
2361
+
2362
+ function supportDiagnosisEvidencePackObject(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2363
+ const ticketAutomation = plainObject(ticket.automation);
2364
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
2365
+ return evidenceObject(
2366
+ job.supportV5DiagnosisEvidencePack,
2367
+ job.support_v5_diagnosis_evidence_pack,
2368
+ job.diagnosisEvidencePack,
2369
+ job.diagnosis_evidence_pack,
2370
+ evidence.supportV5DiagnosisEvidencePack,
2371
+ evidence.support_v5_diagnosis_evidence_pack,
2372
+ evidence.diagnosisEvidencePack,
2373
+ evidence.diagnosis_evidence_pack,
2374
+ ticket.supportV5DiagnosisEvidencePack,
2375
+ ticket.support_v5_diagnosis_evidence_pack,
2376
+ ticket.diagnosisEvidencePack,
2377
+ ticket.diagnosis_evidence_pack,
2378
+ ticketManager.diagnosisEvidencePack,
2379
+ ticketManager.diagnosis_evidence_pack
2380
+ );
2381
+ }
2382
+
2383
+ function supportIssueClassProbePlanObject(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2384
+ const ticketAutomation = plainObject(ticket.automation);
2385
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
2386
+ const autonomousDecision = plainObject(
2387
+ job.supportV5AutonomousDecision
2388
+ || job.support_v5_autonomous_decision
2389
+ || evidence.supportV5AutonomousDecision
2390
+ || evidence.support_v5_autonomous_decision
2391
+ || ticket.supportV5AutonomousDecision
2392
+ || ticket.support_v5_autonomous_decision
2393
+ );
2394
+ return evidenceObject(
2395
+ autonomousDecision.issueClassProbePlan,
2396
+ autonomousDecision.issue_class_probe_plan,
2397
+ job.supportV5IssueClassProbePlan,
2398
+ job.support_v5_issue_class_probe_plan,
2399
+ job.issueClassProbePlan,
2400
+ job.issue_class_probe_plan,
2401
+ evidence.supportV5IssueClassProbePlan,
2402
+ evidence.support_v5_issue_class_probe_plan,
2403
+ evidence.issueClassProbePlan,
2404
+ evidence.issue_class_probe_plan,
2405
+ ticket.supportV5IssueClassProbePlan,
2406
+ ticket.support_v5_issue_class_probe_plan,
2407
+ ticket.issueClassProbePlan,
2408
+ ticket.issue_class_probe_plan,
2409
+ ticketManager.issueClassProbePlan,
2410
+ ticketManager.issue_class_probe_plan
2411
+ );
2412
+ }
2413
+
2414
+ function supportSimilarFixHintsObject(ticket: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2415
+ const ticketAutomation = plainObject(ticket.automation);
2416
+ const ticketManager = plainObject(ticketAutomation.manager || ticket.manager);
2417
+ return evidenceObject(
2418
+ job.supportRootCauseSimilarFixHints,
2419
+ job.support_root_cause_similar_fix_hints,
2420
+ job.similarFixHints,
2421
+ job.similar_fix_hints,
2422
+ evidence.supportRootCauseSimilarFixHints,
2423
+ evidence.support_root_cause_similar_fix_hints,
2424
+ evidence.similarFixHints,
2425
+ evidence.similar_fix_hints,
2426
+ ticket.supportRootCauseSimilarFixHints,
2427
+ ticket.support_root_cause_similar_fix_hints,
2428
+ ticketManager.rootCauseSimilarFixHints,
2429
+ ticketManager.root_cause_similar_fix_hints
2430
+ );
2431
+ }
2432
+
2433
+ function supportDiagnosisEvidencePackGate(pack: Record<string, any>, now?: Date | string): AIRunGateResult | undefined {
2434
+ if (!pack || !Object.keys(pack).length) {
2435
+ return undefined;
2436
+ }
2437
+ const statusText = cleanText(pack.status, 120);
2438
+ const diagnosisValid = pack.diagnosisValid === true || pack.diagnosis_valid === true;
2439
+ const validationBlockers = cleanStringList(pack.validationBlockers || pack.validation_blockers, 40, 500);
2440
+ const similarSelection = plainObject(pack.similarCaseSelection || pack.similar_case_selection);
2441
+ const rankedHints = asArray<any>(similarSelection.ranked);
2442
+ const gateStatus: AIRunGateResult['status'] = diagnosisValid || statusText === 'diagnosis_ready'
2443
+ ? 'pass'
2444
+ : statusText === 'blocked'
2445
+ ? 'blocked'
2446
+ : 'warn';
2447
+ return {
2448
+ key: 'support_diagnosis_evidence_pack',
2449
+ label: 'Support diagnosis evidence pack',
2450
+ status: gateStatus,
2451
+ reason: gateStatus === 'pass'
2452
+ ? 'Support diagnosis evidence pack has a valid root-cause diagnosis and advisory similar-fix context.'
2453
+ : validationBlockers.join('; ') || 'Support diagnosis evidence pack requires read-only root-cause evidence before repair.',
2454
+ evidenceRefs: cleanStringList(pack.requiredEvidence || pack.required_evidence, 30, 500),
2455
+ recordedAt: isoNow(now || pack.generatedAt || pack.generated_at),
2456
+ metadata: {
2457
+ packId: cleanText(pack.packId || pack.pack_id, 180),
2458
+ status: statusText,
2459
+ readOnly: pack.readOnly !== false && pack.read_only !== false,
2460
+ sourceEditsAllowed: pack.sourceEditsAllowed === true || pack.source_edits_allowed === true,
2461
+ rootCauseFirstRequired: pack.rootCauseFirstRequired !== false && pack.root_cause_first_required !== false,
2462
+ requiredOutputKey: cleanText(pack.requiredOutputKey || pack.required_output_key, 120),
2463
+ requiredFields: cleanStringList(pack.requiredFields || pack.required_fields, 20, 160),
2464
+ forbiddenActions: cleanStringList(pack.forbiddenActions || pack.forbidden_actions, 20, 500),
2465
+ diagnosisValid,
2466
+ diagnosisStatus: cleanText(pack.diagnosisStatus || pack.diagnosis_status, 120),
2467
+ validationBlockers,
2468
+ issueClassHint: cleanText(pack.issueClassHint || pack.issue_class_hint, 120),
2469
+ ownerFileHints: cleanStringList(pack.ownerFileHints || pack.owner_file_hints, 20, 500),
2470
+ similarHintCount: rankedHints.length,
2471
+ similarHintsAdvisoryOnly: true,
2472
+ issueClassProbeCount: asArray(pack.issueClassProbeCatalog || pack.issue_class_probe_catalog).length
2473
+ }
2474
+ };
2475
+ }
2476
+
2477
+ function supportIssueClassProbePlanGate(plan: Record<string, any>, diagnosisGate: Record<string, any>, now?: Date | string, required = false): AIRunGateResult | undefined {
2478
+ if (!plan || !Object.keys(plan).length) {
2479
+ if (!required) {
2480
+ return undefined;
2481
+ }
2482
+ const blocker = 'Support V5 run is missing IssueClassProbePlan, so business proof cannot be accepted from route-only or generic QA.';
2483
+ return {
2484
+ key: 'support_issue_class_probe_plan',
2485
+ label: 'Support issue-class probe plan',
2486
+ status: 'blocked',
2487
+ reason: blocker,
2488
+ evidenceRefs: [],
2489
+ recordedAt: isoNow(now),
2490
+ metadata: {
2491
+ required: true,
2492
+ missingPlan: true,
2493
+ issueClassProbePlanReady: false,
2494
+ acceptanceGate: 'aiqa_business_assertion',
2495
+ blockers: [blocker],
2496
+ falsePassBlockers: ['route load only', 'generic clickthrough only', 'scorecard-only pass']
2497
+ }
2498
+ };
2499
+ }
2500
+ const validation = validateResolveIOSupportIssueClassProbePlan(plan, diagnosisGate, now);
2501
+ const normalized = plainObject(validation.normalized || plan);
2502
+ const activeProbe = plainObject(validation.activeProbe || normalized.activeProbe || normalized.active_probe);
2503
+ const blockers = cleanStringList(validation.blockers || normalized.blockers, 40, 500);
2504
+ const requiredArtifacts = cleanStringList(
2505
+ normalized.requiredArtifacts
2506
+ || normalized.required_artifacts
2507
+ || activeProbe.required_artifacts,
2508
+ 30,
2509
+ 500
2510
+ );
2511
+ const falsePassBlockers = cleanStringList(
2512
+ normalized.falsePassBlockers
2513
+ || normalized.false_pass_blockers
2514
+ || activeProbe.false_pass_blockers,
2515
+ 30,
2516
+ 500
2517
+ );
2518
+ const status: AIRunGateResult['status'] = validation.valid ? 'pass' : 'blocked';
2519
+ const issueClass = cleanText(normalized.issue_class || normalized.issueClass || activeProbe.issue_class, 120);
2520
+ const route = cleanText(activeProbe.route, 500);
2521
+ const action = cleanText(activeProbe.action, 1000);
2522
+ return {
2523
+ key: 'support_issue_class_probe_plan',
2524
+ label: 'Support issue-class probe plan',
2525
+ status,
2526
+ reason: validation.valid
2527
+ ? `Support issue-class probe plan is ready for ${issueClass || 'the diagnosed issue'} and requires AIQaBusinessAssertion proof.`
2528
+ : blockers.join('; ') || 'Support issue-class probe plan is incomplete.',
2529
+ evidenceRefs: requiredArtifacts,
2530
+ recordedAt: isoNow(now || normalized.generatedAt || normalized.generated_at),
2531
+ metadata: {
2532
+ planId: cleanText(normalized.planId || normalized.plan_id, 180),
2533
+ status: cleanText(validation.status || normalized.status, 120),
2534
+ issueClass,
2535
+ diagnosisValid: normalized.diagnosisValid === true || normalized.diagnosis_valid === true,
2536
+ issueClassProbePlanReady: validation.valid === true && validation.status === 'ready',
2537
+ activeRoute: route,
2538
+ action,
2539
+ expectedEvidence: cleanText(activeProbe.expected_evidence || activeProbe.expectedEvidence, 1200),
2540
+ expectedBusinessProof: cleanText(activeProbe.expected_business_proof || activeProbe.expectedBusinessProof, 1200),
2541
+ acceptanceGate: cleanText(normalized.acceptanceGate || normalized.acceptance_gate || activeProbe.acceptance_gate, 120),
2542
+ requiredArtifacts,
2543
+ falsePassBlockers,
2544
+ blockers,
2545
+ activeProbe: {
2546
+ issueClass,
2547
+ route,
2548
+ action,
2549
+ probeType: cleanText(activeProbe.probe_type || activeProbe.probeType, 120),
2550
+ failureClass: cleanText(activeProbe.failure_class || activeProbe.failureClass, 120),
2551
+ blocksAcceptanceWithoutBusinessAssertion: activeProbe.blocks_acceptance_without_business_assertion === true
2552
+ || activeProbe.blocksAcceptanceWithoutBusinessAssertion === true
2553
+ }
2554
+ }
2555
+ };
2556
+ }
2557
+
2558
+ function firstNonEmptyText(values: any[], max = 1000): string | undefined {
2559
+ for (const value of values) {
2560
+ const normalized = idText(value, max);
2561
+ if (normalized) {
2562
+ return normalized;
2563
+ }
2564
+ }
2565
+ return undefined;
2566
+ }
2567
+
2568
+ function booleanFlag(value: any): boolean | undefined {
2569
+ if (value === true || value === false) {
2570
+ return value;
2571
+ }
2572
+ if (typeof value === 'string') {
2573
+ const normalized = value.trim().toLowerCase();
2574
+ if (/^(true|yes|1|passed|pass|ready|safe)$/.test(normalized)) {
2575
+ return true;
2576
+ }
2577
+ if (/^(false|no|0|failed|fail|blocked|unsafe)$/.test(normalized)) {
2578
+ return false;
2579
+ }
2580
+ }
2581
+ return undefined;
2582
+ }
2583
+
2584
+ function numberValue(value: any): number | undefined {
2585
+ const parsed = Number(value);
2586
+ return Number.isFinite(parsed) ? parsed : undefined;
2587
+ }
2588
+
2589
+ function buildAICoderQaEvidence(input: AICoderAIRunAdapterInput): Record<string, any> {
2590
+ const app = input.app || {};
2591
+ const job = input.job || {};
2592
+ const workflowMemory = plainObject(
2593
+ job.aiCoderV6WorkflowMemory
2594
+ || job.ai_coder_v6_workflow_memory
2595
+ || app.aiCoderV6WorkflowMemory
2596
+ || app.ai_coder_v6_workflow_memory
2597
+ );
2598
+ const evidence = mergeEvidenceRecords(
2599
+ job.qaEvidence,
2600
+ job.qa_evidence,
2601
+ app.qaEvidence,
2602
+ app.qa_evidence,
2603
+ input.qaEvidence
2604
+ );
2605
+ evidence.workflowQaRows = [
2606
+ ...asArray(workflowMemory.workflowQaRows),
2607
+ ...asArray(workflowMemory.workflow_qa_rows),
2608
+ ...asArray(job.workflowQaRows),
2609
+ ...asArray(job.workflow_qa_rows),
2610
+ ...asArray(app.workflowQaRows),
2611
+ ...asArray(app.workflow_qa_rows),
2612
+ ...asArray(evidence.workflowQaRows),
2613
+ ...asArray(evidence.workflow_qa_rows)
2614
+ ];
2615
+ evidence.businessAssertions = [
2616
+ ...asArray(job.aiQaBusinessAssertions),
2617
+ ...asArray(job.ai_qa_business_assertions),
2618
+ ...asArray(job.businessAssertions),
2619
+ ...asArray(job.business_assertions),
2620
+ ...asArray(app.businessAssertions),
2621
+ ...asArray(app.business_assertions),
2622
+ ...asArray(evidence.businessAssertions),
2623
+ ...asArray(evidence.business_assertions)
2624
+ ];
2625
+ evidence.routeProbes = [
2626
+ ...asArray(job.workflowProbes),
2627
+ ...asArray(job.workflow_probes),
2628
+ ...asArray(evidence.routeProbes),
2629
+ ...asArray(evidence.route_probes)
2630
+ ];
2631
+ evidence.artifactPaths = [
2632
+ ...asArray(workflowMemory.businessProofArtifacts),
2633
+ ...asArray(workflowMemory.business_proof_artifacts),
2634
+ ...asArray(job.artifactPaths),
2635
+ ...asArray(job.artifact_paths),
2636
+ ...asArray(app.artifactPaths),
2637
+ ...asArray(app.artifact_paths),
2638
+ ...asArray(evidence.artifactPaths),
2639
+ ...asArray(evidence.artifact_paths)
2640
+ ];
2641
+ evidence.journeyContract = evidence.journeyContract
2642
+ || evidence.journey_contract
2643
+ || job.journeyContract
2644
+ || job.journey_contract
2645
+ || app.journeyContract
2646
+ || app.journey_contract
2647
+ || workflowMemory.journeyContract
2648
+ || workflowMemory.journey_contract;
2649
+ evidence.journeyContractMarkdown = evidence.journeyContractMarkdown
2650
+ || evidence.journey_contract_markdown
2651
+ || job.journeyContractMarkdown
2652
+ || job.journey_contract_markdown
2653
+ || app.journeyContractMarkdown
2654
+ || app.journey_contract_markdown;
2655
+ evidence.journeyContractPath = firstNonEmptyText([
2656
+ evidence.journeyContractPath,
2657
+ evidence.journey_contract_path,
2658
+ workflowMemory.journeyContractPath,
2659
+ workflowMemory.journey_contract_path,
2660
+ job.journeyContractPath,
2661
+ job.journey_contract_path,
2662
+ app.journeyContractPath,
2663
+ app.journey_contract_path
2664
+ ], 500) || 'docs/APP_JOURNEY_CONTRACT.md';
2665
+ evidence.deployStatus = firstNonEmptyText([
2666
+ evidence.deployStatus,
2667
+ evidence.deploy_status,
2668
+ job.deployStatus,
2669
+ job.deploy_status,
2670
+ job.testDeployStatus,
2671
+ job.test_deploy_status,
2672
+ app.deployStatus,
2673
+ app.deploy_status
2674
+ ], 160);
2675
+ evidence.publishStatus = firstNonEmptyText([
2676
+ evidence.publishStatus,
2677
+ evidence.publish_status,
2678
+ evidence.pullRequestStatus,
2679
+ evidence.pull_request_status,
2680
+ job.publishStatus,
2681
+ job.publish_status,
2682
+ job.pullRequestStatus,
2683
+ job.pull_request_status,
2684
+ app.publishStatus,
2685
+ app.publish_status
2686
+ ], 160);
2687
+ evidence.sampleDataStatus = firstNonEmptyText([
2688
+ evidence.sampleDataStatus,
2689
+ evidence.sample_data_status,
2690
+ job.sampleDataStatus,
2691
+ job.sample_data_status,
2692
+ app.sampleDataStatus,
2693
+ app.sample_data_status
2694
+ ], 160) || statusFromBoolean(evidence.sampleDataReady ?? evidence.hasSampleData ?? job.sampleDataReady ?? app.sampleDataReady);
2695
+ return evidence;
2696
+ }
2697
+
2698
+ function normalizeAICoderWorkflowProofReadiness(
2699
+ value: Record<string, any>,
2700
+ now?: Date | string
2701
+ ): ResolveIOAICoderWorkflowProofReadiness | undefined {
2702
+ if (!value || !Object.keys(value).length) {
2703
+ return undefined;
2704
+ }
2705
+ const ready = value.ready === true;
2706
+ const status = cleanText(value.status, 120) || (ready ? 'ready' : 'blocked');
2707
+ return {
2708
+ ready,
2709
+ status: status as ResolveIOAICoderWorkflowProofReadiness['status'],
2710
+ reason: cleanText(value.reason || value.summary || status, 1200),
2711
+ blockers: cleanStringList(value.blockers, 40, 500),
2712
+ journeyContractValid: value.journeyContractValid === true || value.journey_contract_valid === true,
2713
+ primaryWorkflowId: cleanText(value.primaryWorkflowId || value.primary_workflow_id, 200),
2714
+ passedWorkflowRows: cleanStringList(value.passedWorkflowRows || value.passed_workflow_rows, 80, 300),
2715
+ missingWorkflowRows: cleanStringList(value.missingWorkflowRows || value.missing_workflow_rows, 80, 300),
2716
+ failedWorkflowRows: cleanStringList(value.failedWorkflowRows || value.failed_workflow_rows, 80, 300),
2717
+ passedBusinessAssertions: cleanStringList(value.passedBusinessAssertions || value.passed_business_assertions, 80, 500),
2718
+ routeOnly: value.routeOnly === true || value.route_only === true,
2719
+ scorecardOnly: value.scorecardOnly === true || value.scorecard_only === true,
2720
+ sampleDataReady: value.sampleDataReady === true || value.sample_data_ready === true,
2721
+ releaseBlockers: cleanStringList(value.releaseBlockers || value.release_blockers, 40, 500),
2722
+ artifactPaths: cleanStringList(value.artifactPaths || value.artifact_paths, 80, 500),
2723
+ workflowProofFingerprint: cleanText(value.workflowProofFingerprint || value.workflow_proof_fingerprint, 200),
2724
+ artifactFingerprint: cleanText(value.artifactFingerprint || value.artifact_fingerprint, 200),
2725
+ proofFreshness: cleanText(value.proofFreshness || value.proof_freshness, 80) as ResolveIOAICoderWorkflowProofReadiness['proofFreshness'],
2726
+ nextAction: cleanText(value.nextAction || value.next_action, 1000),
2727
+ evaluatedAt: isoNow(now || value.evaluatedAt || value.evaluated_at || value.recordedAt || value.recorded_at)
2728
+ };
2729
+ }
2730
+
2731
+ function aicoderJourneyContractValidationFromEvidence(evidence: Record<string, any>): ResolveIOAICoderJourneyContractValidationResult {
2732
+ const journeyInput = evidence.journeyContract !== undefined && evidence.journeyContract !== null
2733
+ ? evidence.journeyContract
2734
+ : evidence.journeyContractMarkdown;
2735
+ const hasJourneyInput = journeyInput !== undefined && journeyInput !== null && cleanText(journeyInput, 100).length > 0;
2736
+ if (!hasJourneyInput) {
2737
+ return {
2738
+ valid: false,
2739
+ contract: null,
2740
+ issues: [{
2741
+ code: 'missing_contract',
2742
+ path: cleanText(evidence.journeyContractPath, 500) || 'docs/APP_JOURNEY_CONTRACT.md',
2743
+ message: 'docs/APP_JOURNEY_CONTRACT.md with structured journey_contract JSON is required before AICoder build, workflow QA, wow UI, publish, or acceptance.',
2744
+ severity: 'error'
2745
+ }],
2746
+ workflowQaRows: [],
2747
+ primaryWorkflowId: ''
2748
+ };
2749
+ }
2750
+ return validateResolveIOAICoderJourneyContract(journeyInput, {
2751
+ requireMarkdownEnvelope: typeof journeyInput === 'string'
2752
+ });
2753
+ }
2754
+
2755
+ function aicoderJourneyContractGate(
2756
+ validation: ResolveIOAICoderJourneyContractValidationResult,
2757
+ evidence: Record<string, any>,
2758
+ now?: Date | string
2759
+ ): AIRunGateResult {
2760
+ const errorIssues = validation.issues.filter((issue) => issue.severity === 'error');
2761
+ const warningIssues = validation.issues.filter((issue) => issue.severity === 'warning');
2762
+ const status: AIRunGateResult['status'] = validation.valid
2763
+ ? (warningIssues.length ? 'warn' : 'pass')
2764
+ : 'blocked';
2765
+ const path = cleanText(evidence.journeyContractPath, 500) || 'docs/APP_JOURNEY_CONTRACT.md';
2766
+ return {
2767
+ key: 'aicoder_journey_contract',
2768
+ label: 'AICoder journey contract',
2769
+ status,
2770
+ reason: validation.valid
2771
+ ? (warningIssues.length
2772
+ ? `Journey contract is valid with warnings: ${warningIssues.map((issue) => issue.message).slice(0, 3).join(' | ')}`
2773
+ : 'Journey contract validates first/next/last workflow, data story, completion states, and QA assertions.')
2774
+ : (errorIssues.map((issue) => issue.message).slice(0, 5).join(' | ') || 'AICoder Journey Contract is missing or invalid.'),
2775
+ evidenceRefs: [path],
2776
+ recordedAt: isoNow(now),
2777
+ metadata: {
2778
+ path,
2779
+ valid: validation.valid,
2780
+ primaryWorkflowId: cleanText(validation.primaryWorkflowId, 200),
2781
+ workflowQaRowCount: validation.workflowQaRows.length,
2782
+ workflowQaCoverageTags: cleanStringList(validation.workflowQaRows.flatMap((row: any) => asArray(row.coverageTags || row.coverage_tags)), 80, 120),
2783
+ workflowQaProofKinds: cleanStringList(validation.workflowQaRows.map((row: any) => row.proofKind || row.proof_kind), 80, 120),
2784
+ errorCount: errorIssues.length,
2785
+ warningCount: warningIssues.length,
2786
+ issueCodes: cleanStringList(validation.issues.map((issue) => issue.code), 40, 120),
2787
+ issues: validation.issues.slice(0, 20).map((issue) => ({
2788
+ code: cleanText(issue.code, 120),
2789
+ path: cleanText(issue.path, 500),
2790
+ severity: issue.severity,
2791
+ message: cleanText(issue.message, 800)
2792
+ })),
2793
+ blocksBuildUntilValid: !validation.valid,
2794
+ blocksWorkflowQaUntilValid: !validation.valid,
2795
+ blocksWowUiUntilWorkflowProof: !validation.valid,
2796
+ blocksPublishUntilWorkflowProof: !validation.valid,
2797
+ nextAction: validation.valid
2798
+ ? 'Generate workflow QA rows from journey_contract.qa_assertions and execute the north-star workflow.'
2799
+ : 'Generate or repair docs/APP_JOURNEY_CONTRACT.md before app build, wow UI, publish, or acceptance.'
2800
+ }
2801
+ };
2802
+ }
2803
+
2804
+ function aicoderWorkflowProofReadinessGate(
2805
+ readiness: ResolveIOAICoderWorkflowProofReadiness,
2806
+ now?: Date | string
2807
+ ): AIRunGateResult {
2808
+ const status: AIRunGateResult['status'] = readiness.ready
2809
+ ? 'pass'
2810
+ : readiness.status === 'release_blocked' || readiness.status === 'journey_invalid' || readiness.status === 'sample_data_missing'
2811
+ ? 'fail'
2812
+ : 'blocked';
2813
+ return {
2814
+ key: 'aicoder_workflow_proof_readiness',
2815
+ label: 'AICoder workflow proof readiness',
2816
+ status,
2817
+ reason: readiness.reason || readiness.blockers.join('; ') || readiness.status,
2818
+ evidenceRefs: readiness.artifactPaths,
2819
+ recordedAt: isoNow(now || readiness.evaluatedAt),
2820
+ metadata: {
2821
+ ready: readiness.ready,
2822
+ status: readiness.status,
2823
+ primaryWorkflowId: readiness.primaryWorkflowId,
2824
+ journeyContractValid: readiness.journeyContractValid,
2825
+ blockers: readiness.blockers,
2826
+ passedWorkflowRows: readiness.passedWorkflowRows,
2827
+ missingWorkflowRows: readiness.missingWorkflowRows,
2828
+ failedWorkflowRows: readiness.failedWorkflowRows,
2829
+ passedBusinessAssertions: readiness.passedBusinessAssertions,
2830
+ routeOnly: readiness.routeOnly,
2831
+ scorecardOnly: readiness.scorecardOnly,
2832
+ sampleDataReady: readiness.sampleDataReady,
2833
+ releaseBlockers: readiness.releaseBlockers,
2834
+ workflowProofFingerprint: readiness.workflowProofFingerprint,
2835
+ artifactFingerprint: readiness.artifactFingerprint,
2836
+ proofFreshness: readiness.proofFreshness,
2837
+ nextAction: readiness.nextAction
2838
+ }
2839
+ };
2840
+ }
2841
+
2842
+ function aicoderWorkflowProofCheckpointObject(app: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2843
+ const workflowMemory = plainObject(
2844
+ job.aiCoderV6WorkflowMemory
2845
+ || job.ai_coder_v6_workflow_memory
2846
+ || app.aiCoderV6WorkflowMemory
2847
+ || app.ai_coder_v6_workflow_memory
2848
+ || evidence.aiCoderV6WorkflowMemory
2849
+ || evidence.ai_coder_v6_workflow_memory
2850
+ );
2851
+ const continuationDecision = plainObject(
2852
+ job.aicoderV6ContinuationDecision
2853
+ || job.aiCoderV6ContinuationDecision
2854
+ || job.aicoder_v6_continuation_decision
2855
+ || job.ai_coder_v6_continuation_decision
2856
+ || app.aicoderV6ContinuationDecision
2857
+ || app.aiCoderV6ContinuationDecision
2858
+ || app.aicoder_v6_continuation_decision
2859
+ || evidence.aicoderV6ContinuationDecision
2860
+ || evidence.aiCoderV6ContinuationDecision
2861
+ || evidence.aicoder_v6_continuation_decision
2862
+ );
2863
+ return evidenceObject(
2864
+ continuationDecision.workflowProofCheckpoint,
2865
+ continuationDecision.workflow_proof_checkpoint,
2866
+ workflowMemory.workflowProofCheckpoint,
2867
+ workflowMemory.workflow_proof_checkpoint,
2868
+ job.workflowProofCheckpoint,
2869
+ job.workflow_proof_checkpoint,
2870
+ job.aicoderWorkflowProofCheckpoint,
2871
+ job.aicoder_workflow_proof_checkpoint,
2872
+ app.workflowProofCheckpoint,
2873
+ app.workflow_proof_checkpoint,
2874
+ evidence.workflowProofCheckpoint,
2875
+ evidence.workflow_proof_checkpoint,
2876
+ evidence.aicoderWorkflowProofCheckpoint,
2877
+ evidence.aicoder_workflow_proof_checkpoint
2878
+ );
2879
+ }
2880
+
2881
+ function aicoderWorkflowProofCheckpointGate(checkpoint: Record<string, any>, now?: Date | string): AIRunGateResult | undefined {
2882
+ if (!checkpoint || !Object.keys(checkpoint).length) {
2883
+ return undefined;
2884
+ }
2885
+ const blocksProductRepairUntilJourneyContract = booleanFlag(checkpoint.blocksProductRepairUntilJourneyContract ?? checkpoint.blocks_product_repair_until_journey_contract) === true;
2886
+ const blocksPublishUntilWorkflowProof = booleanFlag(checkpoint.blocksPublishUntilWorkflowProof ?? checkpoint.blocks_publish_until_workflow_proof) === true;
2887
+ const blocksPublishUntilReleaseGate = booleanFlag(checkpoint.blocksPublishUntilReleaseGate ?? checkpoint.blocks_publish_until_release_gate) === true;
2888
+ const blocksWowUiUntilWorkflowProof = booleanFlag(checkpoint.blocksWowUiUntilWorkflowProof ?? checkpoint.blocks_wow_ui_until_workflow_proof) === true;
2889
+ const statusText = cleanText(checkpoint.status, 120);
2890
+ const required = booleanFlag(checkpoint.required) !== false;
2891
+ const blockers = cleanStringList(checkpoint.blockers, 40, 500);
2892
+ if (blocksProductRepairUntilJourneyContract) {
2893
+ blockers.push('Product repair is blocked until the journey contract is valid.');
2894
+ }
2895
+ if (blocksPublishUntilWorkflowProof) {
2896
+ blockers.push('Publish is blocked until workflow business proof passes.');
2897
+ }
2898
+ if (blocksPublishUntilReleaseGate) {
2899
+ blockers.push('Publish is blocked until release gate evidence passes.');
2900
+ }
2901
+ if (blocksWowUiUntilWorkflowProof) {
2902
+ blockers.push('Wow UI polish is blocked until workflow proof exists.');
2903
+ }
2904
+ const gateStatus: AIRunGateResult['status'] = !required || statusText === 'ready_to_continue'
2905
+ ? 'pass'
2906
+ : blockers.length
2907
+ ? 'blocked'
2908
+ : 'warn';
2909
+ return {
2910
+ key: 'aicoder_workflow_proof_checkpoint',
2911
+ label: 'AICoder workflow proof checkpoint',
2912
+ status: gateStatus,
2913
+ reason: gateStatus === 'pass'
2914
+ ? 'AICoder workflow checkpoint allows the next action.'
2915
+ : (blockers.join(' ') || cleanText(checkpoint.nextAction || checkpoint.next_action || statusText, 1200) || 'AICoder workflow checkpoint requires more proof before continuing.'),
2916
+ evidenceRefs: cleanStringList([
2917
+ ...asArray(checkpoint.requiredEvidence || checkpoint.required_evidence),
2918
+ ...asArray(checkpoint.requiredResetEvidence || checkpoint.required_reset_evidence)
2919
+ ], 40, 500),
2920
+ recordedAt: isoNow(now || checkpoint.recordedAt || checkpoint.recorded_at || checkpoint.createdAt || checkpoint.created_at),
2921
+ metadata: {
2922
+ required,
2923
+ status: statusText,
2924
+ readinessStatus: cleanText(checkpoint.readinessStatus || checkpoint.readiness_status, 120),
2925
+ nextGate: cleanText(checkpoint.nextGate || checkpoint.next_gate, 120),
2926
+ nextAction: cleanText(checkpoint.nextAction || checkpoint.next_action, 1000),
2927
+ startingFailureClass: cleanText(checkpoint.startingFailureClass || checkpoint.starting_failure_class, 120),
2928
+ startingBlockerFingerprint: cleanText(checkpoint.startingBlockerFingerprint || checkpoint.starting_blocker_fingerprint, 200),
2929
+ startingEvidenceHash: cleanText(checkpoint.startingEvidenceHash || checkpoint.starting_evidence_hash, 200),
2930
+ workflowProofFingerprint: cleanText(checkpoint.workflowProofFingerprint || checkpoint.workflow_proof_fingerprint, 200),
2931
+ artifactFingerprint: cleanText(checkpoint.artifactFingerprint || checkpoint.artifact_fingerprint, 200),
2932
+ requiredEvidence: cleanStringList(checkpoint.requiredEvidence || checkpoint.required_evidence, 40, 500),
2933
+ requiredResetEvidence: cleanStringList(checkpoint.requiredResetEvidence || checkpoint.required_reset_evidence, 40, 500),
2934
+ successRequiresWorkflowBusinessProof: booleanFlag(checkpoint.successRequiresWorkflowBusinessProof ?? checkpoint.success_requires_workflow_business_proof) === true,
2935
+ blocksProductRepairUntilJourneyContract,
2936
+ blocksPublishUntilWorkflowProof,
2937
+ blocksPublishUntilReleaseGate,
2938
+ blocksWowUiUntilWorkflowProof,
2939
+ blockers
2940
+ }
2941
+ };
2942
+ }
2943
+
2944
+ function aicoderNextActionContractObject(app: Record<string, any>, job: Record<string, any>, evidence: Record<string, any>): Record<string, any> {
2945
+ const continuationDecision = plainObject(
2946
+ job.aicoderV6ContinuationDecision
2947
+ || job.aiCoderV6ContinuationDecision
2948
+ || job.aicoder_v6_continuation_decision
2949
+ || job.ai_coder_v6_continuation_decision
2950
+ || app.aicoderV6ContinuationDecision
2951
+ || app.aiCoderV6ContinuationDecision
2952
+ || app.aicoder_v6_continuation_decision
2953
+ || evidence.aicoderV6ContinuationDecision
2954
+ || evidence.aiCoderV6ContinuationDecision
2955
+ || evidence.aicoder_v6_continuation_decision
2956
+ );
2957
+ return evidenceObject(
2958
+ continuationDecision.nextActionContract,
2959
+ continuationDecision.next_action_contract,
2960
+ job.nextActionContract,
2961
+ job.next_action_contract,
2962
+ job.aicoderNextActionContract,
2963
+ job.aicoder_next_action_contract,
2964
+ job.aiCoderNextActionContract,
2965
+ job.ai_coder_next_action_contract,
2966
+ app.nextActionContract,
2967
+ app.next_action_contract,
2968
+ evidence.nextActionContract,
2969
+ evidence.next_action_contract,
2970
+ evidence.aicoderNextActionContract,
2971
+ evidence.aicoder_next_action_contract
2972
+ );
2973
+ }
2974
+
2975
+ function aicoderNextActionContractGate(contract: Record<string, any>, now?: Date | string): AIRunGateResult | undefined {
2976
+ if (!contract || !Object.keys(contract).length) {
2977
+ return undefined;
2978
+ }
2979
+ const safeToAutoRun = booleanFlag(contract.safeToAutoRun ?? contract.safe_to_auto_run) === true;
2980
+ const requiresHumanApproval = booleanFlag(contract.requiresHumanApproval ?? contract.requires_human_approval) === true;
2981
+ const canRunWithoutCodexMonitor = booleanFlag(contract.canRunWithoutCodexMonitor ?? contract.can_run_without_codex_monitor) === true;
2982
+ const codexFallbackRequired = booleanFlag(contract.codexFallbackRequired ?? contract.codex_fallback_required) === true;
2983
+ const sourceBlockers = cleanStringList(contract.blockers, 40, 500);
2984
+ const primaryCommand = cleanText(contract.primaryCommand || contract.primary_command, 200);
2985
+ const liveHotfixBlockedUntilCommit = booleanFlag(contract.liveHotfixBlockedUntilCommit ?? contract.live_hotfix_blocked_until_commit ?? plainObject(contract.decisionBasis || contract.decision_basis).liveHotfixBlockedUntilCommit ?? plainObject(contract.decisionBasis || contract.decision_basis).live_hotfix_blocked_until_commit) === true;
2986
+ const safetyBlockers = Array.from(new Set([
2987
+ ...(safeToAutoRun ? [] : ['AICoder next action is not marked safe to auto-run.']),
2988
+ ...(canRunWithoutCodexMonitor ? [] : ['AICoder next action requires Codex fallback/monitoring instead of manager-only execution.']),
2989
+ ...(codexFallbackRequired ? [cleanText(contract.codexFallbackReason || contract.codex_fallback_reason, 1000) || 'AICoder next action contract requires Codex fallback.'] : []),
2990
+ ...(requiresHumanApproval ? ['AICoder next action requires human approval.'] : []),
2991
+ ...(liveHotfixBlockedUntilCommit ? ['Live AICoder hotfix is blocked until GitHub commit and push proof is recorded.'] : [])
2992
+ ])).filter(Boolean).slice(0, 40);
2993
+ const contractBlockers = safetyBlockers.length
2994
+ ? Array.from(new Set([...sourceBlockers, ...safetyBlockers])).slice(0, 40)
2995
+ : [];
2996
+ const missingSafetyFlags = [
2997
+ contract.safeToAutoRun === undefined && contract.safe_to_auto_run === undefined ? 'safeToAutoRun' : '',
2998
+ contract.canRunWithoutCodexMonitor === undefined && contract.can_run_without_codex_monitor === undefined ? 'canRunWithoutCodexMonitor' : '',
2999
+ contract.codexFallbackRequired === undefined && contract.codex_fallback_required === undefined ? 'codexFallbackRequired' : ''
3000
+ ].filter(Boolean);
3001
+ const gateStatus: AIRunGateResult['status'] = contractBlockers.length
3002
+ ? 'blocked'
3003
+ : missingSafetyFlags.length
3004
+ ? 'warn'
3005
+ : 'pass';
3006
+ return {
3007
+ key: 'aicoder_next_action_contract',
3008
+ label: 'AICoder next action contract',
3009
+ status: gateStatus,
3010
+ reason: gateStatus === 'pass'
3011
+ ? `AICoder can run ${primaryCommand || 'the next action'} without a Codex monitor.`
3012
+ : (contractBlockers.join(' ') || cleanText(contract.codexFallbackReason || contract.codex_fallback_reason || contract.expectedStateTransition || contract.expected_state_transition, 1200) || 'AICoder next action is not safe to run unattended.'),
3013
+ evidenceRefs: cleanStringList([
3014
+ ...asArray(contract.preconditions),
3015
+ ...asArray(contract.successEvidence || contract.success_evidence),
3016
+ ...asArray(contract.stopConditions || contract.stop_conditions)
3017
+ ], 40, 500),
3018
+ recordedAt: isoNow(now || contract.createdAt || contract.created_at || contract.recordedAt || contract.recorded_at),
3019
+ metadata: {
3020
+ contractId: cleanText(contract.contractId || contract.contract_id, 200),
3021
+ action: cleanText(contract.action, 120),
3022
+ label: cleanText(contract.label, 200),
3023
+ primaryCommand,
3024
+ lane: cleanText(contract.lane, 120),
3025
+ stepType: cleanText(contract.stepType || contract.step_type, 120),
3026
+ safeToAutoRun,
3027
+ requiresHumanApproval,
3028
+ canRunWithoutCodexMonitor,
3029
+ codexFallbackRequired,
3030
+ codexFallbackReason: cleanText(contract.codexFallbackReason || contract.codex_fallback_reason, 1000),
3031
+ costRisk: cleanText(contract.costRisk || contract.cost_risk, 120),
3032
+ workflowFirstSatisfied: booleanFlag(contract.workflowFirstSatisfied ?? contract.workflow_first_satisfied) === true,
3033
+ hotfixCommitRequired: booleanFlag(contract.hotfixCommitRequired ?? contract.hotfix_commit_required ?? plainObject(contract.decisionBasis || contract.decision_basis).hotfixCommitRequired ?? plainObject(contract.decisionBasis || contract.decision_basis).hotfix_commit_required) === true,
3034
+ liveHotfixBlockedUntilCommit,
3035
+ decisionBasis: plainObject(contract.decisionBasis || contract.decision_basis),
3036
+ preconditions: cleanStringList(contract.preconditions, 40, 500),
3037
+ expectedStateTransition: cleanText(contract.expectedStateTransition || contract.expected_state_transition, 1000),
3038
+ successEvidence: cleanStringList(contract.successEvidence || contract.success_evidence, 40, 500),
3039
+ requiredHotfixCommitProof: cleanStringList(contract.requiredHotfixCommitProof || contract.required_hotfix_commit_proof, 20, 240),
3040
+ stopConditions: cleanStringList(contract.stopConditions || contract.stop_conditions, 40, 500),
3041
+ forbiddenActions: cleanStringList(contract.forbiddenActions || contract.forbidden_actions, 40, 500),
3042
+ nextCommands: cleanStringList(contract.nextCommands || contract.next_commands, 40, 240),
3043
+ blockers: contractBlockers,
3044
+ contextBlockers: sourceBlockers,
3045
+ safetyBlockers,
3046
+ missingSafetyFlags,
3047
+ ownerFiles: cleanStringList(contract.ownerFiles || contract.owner_files, 40, 500)
3048
+ }
3049
+ };
3050
+ }
3051
+
3052
+ function adapterStatusPassed(value: any): boolean {
3053
+ const normalized = cleanText(value, 120).toLowerCase();
3054
+ return !!normalized && /(pass|passed|success|succeeded|ok|done|complete|completed|ready|saved|published|deployed)/i.test(normalized);
3055
+ }
3056
+
3057
+ function applyAICoderWorkflowProofGate(
3058
+ qa: AIQaRun,
3059
+ readiness: ResolveIOAICoderWorkflowProofReadiness,
3060
+ now?: Date | string
3061
+ ): AIQaRun {
3062
+ if (
3063
+ readiness.ready
3064
+ || readiness.status === 'release_blocked'
3065
+ || qa.outcome === 'infra_failed'
3066
+ || qa.outcome === 'compile_failed'
3067
+ || qa.outcome === 'route_failed'
3068
+ || qa.outcome === 'business_assertion_failed'
3069
+ ) {
3070
+ return qa;
3071
+ }
3072
+ const gate = aicoderWorkflowProofReadinessGate(readiness, now);
3073
+ return {
3074
+ ...qa,
3075
+ outcome: qa.routeProbes.some((probe) => adapterStatusPassed(probe.status))
3076
+ ? 'route_only_pass'
3077
+ : 'incomplete',
3078
+ gateResults: [
3079
+ ...qa.gateResults.filter((existing) => !(existing.key === 'qa_business_assertion' && existing.status === 'pass')),
3080
+ gate
3081
+ ]
3082
+ };
3083
+ }
3084
+
3085
+ function assistantQualityObject(input: AssistantAIRunAdapterInput): Record<string, any> {
3086
+ const conversation = input.conversation || {};
3087
+ const latestAssistantMessage = [...asArray<Record<string, any>>(input.messages)]
3088
+ .reverse()
3089
+ .find((message) => /assistant/i.test(cleanText(message.role || message.authorRole || message.type, 80)));
3090
+ return plainObject(
3091
+ input.answerQuality
3092
+ || conversation.answerQuality
3093
+ || conversation.answer_quality
3094
+ || conversation.assistantAnswerQuality
3095
+ || conversation.assistant_answer_quality
3096
+ || latestAssistantMessage?.answerQuality
3097
+ || latestAssistantMessage?.answer_quality
3098
+ || latestAssistantMessage?.metadata?.answerQuality
3099
+ || latestAssistantMessage?.metadata?.answer_quality
3100
+ );
3101
+ }
3102
+
3103
+ function assistantConfidenceLevel(value: any): AssistantAnswerQualityDecision['confidenceLevel'] {
3104
+ const normalized = cleanText(value?.level || value?.confidenceLevel || value?.confidence_level || value, 80).toLowerCase();
3105
+ if (/^high$/.test(normalized)) {
3106
+ return 'high';
3107
+ }
3108
+ if (/^medium$/.test(normalized)) {
3109
+ return 'medium';
3110
+ }
3111
+ if (/^low$/.test(normalized)) {
3112
+ return 'low';
3113
+ }
3114
+ return 'unknown';
3115
+ }
3116
+
3117
+ function assistantQueryStatus(value: any): AssistantAnswerQualityDecision['queryStatus'] {
3118
+ const normalized = cleanText(value?.status || value?.queryStatus || value?.query_status || value, 120).toLowerCase().replace(/[\s-]+/g, '_');
3119
+ if (/^(ok|success|answered|data_found|data)$/.test(normalized)) {
3120
+ return 'ok';
3121
+ }
3122
+ if (/^(no_data|no_results|empty|not_found)$/.test(normalized)) {
3123
+ return 'no_data';
3124
+ }
3125
+ if (/^(query_error|mongo_error|error|failed)$/.test(normalized)) {
3126
+ return 'query_error';
3127
+ }
3128
+ if (/^(permission_error|permission_denied|forbidden|unauthorized)$/.test(normalized)) {
3129
+ return 'permission_error';
3130
+ }
3131
+ return 'unknown';
3132
+ }
3133
+
3134
+ function asObjectList(value: any): Record<string, any>[] {
3135
+ return asArray<Record<string, any>>(value).filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry));
3136
+ }
3137
+
3138
+ function assistantEvidenceRefs(quality: Record<string, any>): string[] {
3139
+ return cleanStringList([
3140
+ ...asArray(quality.evidenceRefs),
3141
+ ...asArray(quality.evidence_refs),
3142
+ ...asArray(quality.citationRefs),
3143
+ ...asArray(quality.citation_refs),
3144
+ ...asArray(quality.artifactPaths),
3145
+ ...asArray(quality.artifact_paths),
3146
+ ...asObjectList(quality.citations).map((citation) => citation.path || citation.url || citation.id || citation.ref),
3147
+ ...asObjectList(quality.evidence).map((entry) => entry.path || entry.url || entry.id || entry.ref)
3148
+ ], 80, 500);
3149
+ }
3150
+
3151
+ function assistantCitationRefs(quality: Record<string, any>): string[] {
3152
+ return cleanStringList([
3153
+ ...asArray(quality.citationRefs),
3154
+ ...asArray(quality.citation_refs),
3155
+ ...asObjectList(quality.citations).map((citation) => citation.path || citation.url || citation.id || citation.ref),
3156
+ ...asObjectList(quality.evidence).map((entry) => entry.citation || entry.ref || entry.path || entry.url)
3157
+ ], 80, 500);
3158
+ }
3159
+
3160
+ function assistantQueryExecutions(quality: Record<string, any>): Record<string, any>[] {
3161
+ const queryExecution = plainObject(quality.queryExecution || quality.query_execution);
3162
+ const toolResult = plainObject(quality.toolResult || quality.tool_result);
3163
+ return [
3164
+ ...asObjectList(quality.queryExecutions),
3165
+ ...asObjectList(quality.query_executions),
3166
+ ...asObjectList(quality.dataQueries),
3167
+ ...asObjectList(quality.data_queries),
3168
+ ...asObjectList(quality.toolResults),
3169
+ ...asObjectList(quality.tool_results),
3170
+ ...asObjectList(quality.mongoReads),
3171
+ ...asObjectList(quality.mongo_reads),
3172
+ ...asObjectList(quality.mongoAggregations),
3173
+ ...asObjectList(quality.mongo_aggregations),
3174
+ ...(Object.keys(queryExecution).length ? [queryExecution] : []),
3175
+ ...(Object.keys(toolResult).length ? [toolResult] : [])
3176
+ ].filter((entry) => Object.keys(entry).length);
3177
+ }
3178
+
3179
+ function assistantQueryEvidenceRefs(quality: Record<string, any>, executions = assistantQueryExecutions(quality)): string[] {
3180
+ return cleanStringList([
3181
+ ...asArray(quality.queryEvidenceRefs),
3182
+ ...asArray(quality.query_evidence_refs),
3183
+ ...asArray(quality.toolResultRefs),
3184
+ ...asArray(quality.tool_result_refs),
3185
+ ...asArray(quality.queryResultRefs),
3186
+ ...asArray(quality.query_result_refs),
3187
+ ...executions.map((execution) => {
3188
+ return execution.id
3189
+ || execution._id
3190
+ || execution.ref
3191
+ || execution.toolResultId
3192
+ || execution.tool_result_id
3193
+ || execution.toolCallId
3194
+ || execution.tool_call_id
3195
+ || execution.artifactPath
3196
+ || execution.artifact_path
3197
+ || execution.path
3198
+ || execution.url;
3199
+ })
3200
+ ], 80, 500);
3201
+ }
3202
+
3203
+ function assistantNextActions(quality: Record<string, any>): string[] {
3204
+ return cleanStringList([
3205
+ ...asArray(quality.nextActions),
3206
+ ...asArray(quality.next_actions),
3207
+ ...asArray(quality.recommendedActions),
3208
+ ...asArray(quality.recommended_actions)
3209
+ ], 20, 500);
3210
+ }
3211
+
3212
+ function normalizedIsoDay(value: any): string {
3213
+ if (!value) {
3214
+ return '';
3215
+ }
3216
+ const date = value instanceof Date ? value : new Date(value);
3217
+ if (!Number.isFinite(date.getTime())) {
3218
+ return cleanText(value, 40).slice(0, 10);
3219
+ }
3220
+ return date.toISOString().slice(0, 10);
3221
+ }
3222
+
3223
+ function assistantDateWindowObject(quality: Record<string, any>): Record<string, any> {
3224
+ const dateWindow = plainObject(quality.dateWindow || quality.date_window);
3225
+ const nestedWindow = plainObject(quality.verification?.metrics?.window || quality.metrics?.window);
3226
+ return Object.keys(dateWindow).length ? dateWindow : nestedWindow;
3227
+ }
3228
+
3229
+ function collectMongoProjectionObjects(value: any, result: Record<string, any>[] = []): Record<string, any>[] {
3230
+ if (!value || typeof value !== 'object') {
3231
+ return result;
3232
+ }
3233
+ if (Array.isArray(value)) {
3234
+ for (const entry of value) {
3235
+ collectMongoProjectionObjects(entry, result);
3236
+ }
3237
+ return result;
3238
+ }
3239
+ if (value.projection && typeof value.projection === 'object' && !Array.isArray(value.projection)) {
3240
+ result.push(value.projection);
3241
+ }
3242
+ if (value.options && typeof value.options === 'object' && !Array.isArray(value.options) && value.options.projection) {
3243
+ collectMongoProjectionObjects({ projection: value.options.projection }, result);
3244
+ }
3245
+ if (value.query && typeof value.query === 'object') {
3246
+ collectMongoProjectionObjects(value.query, result);
3247
+ }
3248
+ if (value.mongoQuery && typeof value.mongoQuery === 'object') {
3249
+ collectMongoProjectionObjects(value.mongoQuery, result);
3250
+ }
3251
+ if (value.mongo_query && typeof value.mongo_query === 'object') {
3252
+ collectMongoProjectionObjects(value.mongo_query, result);
3253
+ }
3254
+ return result;
3255
+ }
3256
+
3257
+ function projectionHasPositionalOperator(projection: Record<string, any>): boolean {
3258
+ return Object.keys(projection || {}).some((key) => /\.(?:\$|\$\[)|\.\$$/.test(key));
3259
+ }
3260
+
3261
+ function assistantQueryShapeLegal(quality: Record<string, any>): boolean {
3262
+ if (quality.legalQueryShape === true || quality.legal_query_shape === true) {
3263
+ return true;
3264
+ }
3265
+ if (quality.legalQueryShape === false || quality.legal_query_shape === false || quality.illegalQueryShape === true || quality.illegal_query_shape === true) {
3266
+ return false;
3267
+ }
3268
+ const queryShape = plainObject(quality.queryShape || quality.query_shape);
3269
+ if (queryShape.legal === true || queryShape.status === 'pass') {
3270
+ return true;
3271
+ }
3272
+ if (queryShape.legal === false || queryShape.status === 'fail') {
3273
+ return false;
3274
+ }
3275
+ const projections = collectMongoProjectionObjects(quality);
3276
+ if (projections.some(projectionHasPositionalOperator)) {
3277
+ return false;
3278
+ }
3279
+ return true;
3280
+ }
3281
+
3282
+ function assistantCorrectnessFailed(checks: Array<Record<string, any>>): string[] {
3283
+ return checks
3284
+ .filter((check) => {
3285
+ const status = cleanText(check.status || (check.passed === true ? 'pass' : check.passed === false ? 'fail' : ''), 80).toLowerCase();
3286
+ return /^(fail|failed|blocked|incorrect|wrong|error)$/.test(status);
3287
+ })
3288
+ .map((check) => cleanText(check.assertion || check.name || check.case || check.reason || 'assistant correctness check failed', 300))
3289
+ .slice(0, 20);
3290
+ }
3291
+
3292
+ export function evaluateAssistantAnswerQuality(input: AssistantAnswerQualityInput = {}): AssistantAnswerQualityDecision {
3293
+ const quality = plainObject(input.answerQuality);
3294
+ const now = isoNow(input.now);
3295
+ const correctnessChecks = asArray<Record<string, any>>(input.correctnessChecks);
3296
+ const failedChecks = assistantCorrectnessFailed(correctnessChecks);
3297
+ const queryStatus = assistantQueryStatus(quality.queryStatus || quality.query_status || quality.queryResult || quality.query_result);
3298
+ const confidenceLevel = assistantConfidenceLevel(quality.confidence || quality.confidenceLevel || quality.confidence_level);
3299
+ const evidenceRefs = assistantEvidenceRefs(quality);
3300
+ const citationRefs = assistantCitationRefs(quality);
3301
+ const queryExecutions = assistantQueryExecutions(quality);
3302
+ const queryEvidenceRefs = assistantQueryEvidenceRefs(quality, queryExecutions);
3303
+ const nextActions = assistantNextActions(quality);
3304
+ const requiresCurrentDate = quality.requiresCurrentDate === true || quality.requires_current_date === true;
3305
+ const dateWindowRequired = quality.requiresDateWindow === true
3306
+ || quality.requires_date_window === true
3307
+ || quality.dateWindowRequired === true
3308
+ || quality.date_window_required === true;
3309
+ const requiresCitations = quality.requiresCitations !== false && quality.requires_citations !== false;
3310
+ const requiresNextAction = quality.requiresNextAction !== false && quality.requires_next_action !== false;
3311
+ const dateWindow = assistantDateWindowObject(quality);
3312
+ const dateWindowStart = cleanText(dateWindow.startDate || dateWindow.start_date || dateWindow.start, 80);
3313
+ const dateWindowEnd = cleanText(dateWindow.endDate || dateWindow.end_date || dateWindow.end, 80);
3314
+ const dateWindowPresent = !!dateWindowStart && !!dateWindowEnd;
3315
+ const dateBasis = cleanText(
3316
+ quality.currentDateUsed
3317
+ || quality.current_date_used
3318
+ || quality.dateBasis
3319
+ || quality.date_basis
3320
+ || quality.dateWindow?.basisDate
3321
+ || quality.date_window?.basis_date,
3322
+ 80
3323
+ );
3324
+ const expectedCurrentDate = cleanText(quality.expectedCurrentDate || quality.expected_current_date, 80)
3325
+ || normalizedIsoDay(input.now);
3326
+ const noDataConfirmed = quality.noDataConfirmed === true
3327
+ || quality.no_data_confirmed === true
3328
+ || quality.confirmedNoData === true
3329
+ || quality.confirmed_no_data === true;
3330
+ const legalQueryShape = assistantQueryShapeLegal(quality);
3331
+ const queryEvidenceRequired = quality.requiresQueryEvidence !== false
3332
+ && quality.requires_query_evidence !== false
3333
+ && (queryStatus === 'ok' || queryStatus === 'no_data');
3334
+ const queryEvidencePresent = queryExecutions.length > 0 || queryEvidenceRefs.length > 0;
3335
+ const blockers: string[] = [];
3336
+ let status: AssistantAnswerQualityStatus = 'ready';
3337
+ let reason = 'Assistant answer has structured data evidence, legal query shape, citations, confidence, and next action.';
3338
+ if (!Object.keys(quality).length) {
3339
+ status = 'missing_data_source';
3340
+ blockers.push('Assistant answerQuality evidence is missing.');
3341
+ reason = 'Assistant answer cannot be accepted without structured answerQuality evidence.';
3342
+ }
3343
+ else if (failedChecks.length) {
3344
+ status = 'incorrect';
3345
+ blockers.push(...failedChecks);
3346
+ reason = 'Assistant correctness checks failed.';
3347
+ }
3348
+ else if (queryStatus === 'permission_error') {
3349
+ status = 'permission_error';
3350
+ blockers.push('Assistant data query was blocked by permission or authorization.');
3351
+ reason = 'Assistant must report permission-error state and avoid claiming no data or success.';
3352
+ }
3353
+ else if (queryStatus === 'query_error') {
3354
+ status = 'query_error';
3355
+ blockers.push('Assistant data query failed.');
3356
+ reason = 'Assistant must report query-error state and avoid claiming no data or success.';
3357
+ }
3358
+ else if (!legalQueryShape) {
3359
+ status = 'illegal_query_shape';
3360
+ blockers.push('Assistant query shape is illegal or uses an unsafe Mongo projection shape.');
3361
+ reason = 'Assistant must use legal Mongo query/projection shapes before answering.';
3362
+ }
3363
+ else if (requiresCurrentDate && (!dateBasis || normalizedIsoDay(dateBasis) !== normalizedIsoDay(expectedCurrentDate))) {
3364
+ status = 'date_incorrect';
3365
+ blockers.push(`Expected current date ${normalizedIsoDay(expectedCurrentDate) || expectedCurrentDate}, got ${dateBasis || 'missing date basis'}.`);
3366
+ reason = 'Assistant answer did not prove it used the actual current date.';
3367
+ }
3368
+ else if (dateWindowRequired && !dateWindowPresent) {
3369
+ status = 'missing_date_window';
3370
+ blockers.push('Assistant dated answer did not record concrete date-window start and end evidence.');
3371
+ reason = 'Assistant dated answers must record the exact query date window used.';
3372
+ }
3373
+ else if (queryStatus === 'unknown') {
3374
+ status = 'missing_data_source';
3375
+ blockers.push('Assistant answer did not record queryStatus ok/no_data/query_error/permission_error.');
3376
+ reason = 'Assistant answer must classify data access result before acceptance.';
3377
+ }
3378
+ else if (queryStatus === 'no_data' && !noDataConfirmed) {
3379
+ status = 'no_data_unverified';
3380
+ blockers.push('No-data answer was not confirmed by a probe or query evidence.');
3381
+ reason = 'Assistant no-data answers require explicit noDataConfirmed evidence.';
3382
+ }
3383
+ else if (queryEvidenceRequired && !queryEvidencePresent) {
3384
+ status = 'missing_query_proof';
3385
+ blockers.push('Assistant data answer did not record a structured query execution, tool result, or query evidence reference.');
3386
+ reason = 'Assistant answers from system data require structured query/tool execution proof before acceptance.';
3387
+ }
3388
+ else if (requiresCitations && !citationRefs.length && !evidenceRefs.length) {
3389
+ status = 'missing_citations';
3390
+ blockers.push('Assistant answer has no cited evidence or artifact reference.');
3391
+ reason = 'Assistant answer must cite system data evidence.';
3392
+ }
3393
+ else if (confidenceLevel === 'low' || confidenceLevel === 'unknown') {
3394
+ status = 'low_confidence';
3395
+ blockers.push('Assistant answer confidence is low or unknown.');
3396
+ reason = 'Assistant answer requires recorded medium/high confidence.';
3397
+ }
3398
+ else if (requiresNextAction && !nextActions.length) {
3399
+ status = 'missing_next_action';
3400
+ blockers.push('Assistant answer did not record next actions.');
3401
+ reason = 'Assistant answer must provide a next action or explicit no-action state.';
3402
+ }
3403
+ return {
3404
+ ready: status === 'ready',
3405
+ status,
3406
+ reason,
3407
+ blockers,
3408
+ queryStatus,
3409
+ confidenceLevel,
3410
+ dateBasis: dateBasis || undefined,
3411
+ expectedCurrentDate: expectedCurrentDate || undefined,
3412
+ dateWindowRequired,
3413
+ dateWindowPresent,
3414
+ dateWindow: dateWindowPresent ? {
3415
+ startDate: dateWindowStart,
3416
+ endDate: dateWindowEnd,
3417
+ mode: cleanText(dateWindow.mode || dateWindow.type, 80) || undefined,
3418
+ months: typeof dateWindow.months === 'number' ? dateWindow.months : undefined
3419
+ } : undefined,
3420
+ noDataConfirmed,
3421
+ legalQueryShape,
3422
+ citationRefs,
3423
+ queryEvidenceRequired,
3424
+ queryEvidencePresent,
3425
+ queryEvidenceRefs,
3426
+ queryExecutionCount: queryExecutions.length,
3427
+ nextActions,
3428
+ evidenceRefs,
3429
+ failedChecks,
3430
+ recordedAt: now
3431
+ };
3432
+ }
3433
+
3434
+ function assistantAnswerQualityGate(
3435
+ decision: AssistantAnswerQualityDecision,
3436
+ now?: Date | string
3437
+ ): AIRunGateResult {
3438
+ const status: AIRunGateResult['status'] = decision.ready
3439
+ ? 'pass'
3440
+ : /incorrect|date_incorrect|missing_date_window|illegal_query_shape/.test(decision.status)
3441
+ ? 'fail'
3442
+ : 'blocked';
3443
+ return {
3444
+ key: 'assistant_answer_quality',
3445
+ label: 'Assistant answer quality',
3446
+ status,
3447
+ reason: decision.reason,
3448
+ evidenceRefs: decision.evidenceRefs.length ? decision.evidenceRefs : decision.citationRefs,
3449
+ recordedAt: isoNow(now || decision.recordedAt),
3450
+ metadata: {
3451
+ ready: decision.ready,
3452
+ status: decision.status,
3453
+ queryStatus: decision.queryStatus,
3454
+ confidenceLevel: decision.confidenceLevel,
3455
+ dateBasis: decision.dateBasis,
3456
+ expectedCurrentDate: decision.expectedCurrentDate,
3457
+ dateWindowRequired: decision.dateWindowRequired,
3458
+ dateWindowPresent: decision.dateWindowPresent,
3459
+ dateWindow: decision.dateWindow,
3460
+ noDataConfirmed: decision.noDataConfirmed,
3461
+ legalQueryShape: decision.legalQueryShape,
3462
+ citationRefs: decision.citationRefs,
3463
+ queryEvidenceRequired: decision.queryEvidenceRequired,
3464
+ queryEvidencePresent: decision.queryEvidencePresent,
3465
+ queryEvidenceRefs: decision.queryEvidenceRefs,
3466
+ queryExecutionCount: decision.queryExecutionCount,
3467
+ nextActions: decision.nextActions,
3468
+ blockers: decision.blockers,
3469
+ failedChecks: decision.failedChecks
3470
+ }
3471
+ };
3472
+ }
3473
+
3474
+ function assistantActionabilityStatus(decision: AssistantAnswerQualityDecision): AssistantAnswerActionabilityStatus {
3475
+ switch (decision.status) {
3476
+ case 'ready':
3477
+ return 'ready_to_answer';
3478
+ case 'query_error':
3479
+ return 'needs_query_repair';
3480
+ case 'permission_error':
3481
+ return 'needs_permission';
3482
+ case 'no_data_unverified':
3483
+ return 'needs_no_data_verification';
3484
+ case 'date_incorrect':
3485
+ case 'missing_date_window':
3486
+ return 'needs_date_window_repair';
3487
+ case 'illegal_query_shape':
3488
+ return 'needs_query_shape_repair';
3489
+ case 'missing_data_source':
3490
+ case 'missing_query_proof':
3491
+ case 'missing_citations':
3492
+ return 'needs_evidence';
3493
+ case 'low_confidence':
3494
+ return 'needs_confidence_review';
3495
+ case 'missing_next_action':
3496
+ return 'needs_next_action';
3497
+ case 'incorrect':
3498
+ return 'incorrect';
3499
+ default:
3500
+ return 'blocked';
3501
+ }
3502
+ }
3503
+
3504
+ function assistantActionabilityCommand(status: AssistantAnswerActionabilityStatus): string {
3505
+ switch (status) {
3506
+ case 'ready_to_answer':
3507
+ return 'answer_from_verified_system_data';
3508
+ case 'needs_query_repair':
3509
+ return 'repair_assistant_query_and_rerun';
3510
+ case 'needs_permission':
3511
+ return 'request_assistant_data_permission';
3512
+ case 'needs_no_data_verification':
3513
+ return 'verify_no_data_with_scoped_probe';
3514
+ case 'needs_date_window_repair':
3515
+ return 'repair_assistant_date_window_and_rerun';
3516
+ case 'needs_query_shape_repair':
3517
+ return 'repair_assistant_mongo_query_shape';
3518
+ case 'needs_confidence_review':
3519
+ return 'review_assistant_confidence_evidence';
3520
+ case 'needs_next_action':
3521
+ return 'add_assistant_next_action';
3522
+ case 'incorrect':
3523
+ return 'repair_assistant_answer_from_failed_check';
3524
+ case 'needs_evidence':
3525
+ case 'blocked':
3526
+ default:
3527
+ return 'collect_assistant_answer_evidence';
3528
+ }
3529
+ }
3530
+
3531
+ function assistantActionabilityLabel(status: AssistantAnswerActionabilityStatus): string {
3532
+ switch (status) {
3533
+ case 'ready_to_answer':
3534
+ return 'Answer From Verified Data';
3535
+ case 'needs_query_repair':
3536
+ return 'Repair Query';
3537
+ case 'needs_permission':
3538
+ return 'Request Data Permission';
3539
+ case 'needs_no_data_verification':
3540
+ return 'Verify No Data';
3541
+ case 'needs_date_window_repair':
3542
+ return 'Repair Date Window';
3543
+ case 'needs_query_shape_repair':
3544
+ return 'Repair Query Shape';
3545
+ case 'needs_confidence_review':
3546
+ return 'Review Confidence';
3547
+ case 'needs_next_action':
3548
+ return 'Add Next Action';
3549
+ case 'incorrect':
3550
+ return 'Repair Incorrect Answer';
3551
+ default:
3552
+ return 'Collect Answer Evidence';
3553
+ }
3554
+ }
3555
+
3556
+ export function buildAssistantAnswerActionabilityContract(
3557
+ decision: AssistantAnswerQualityDecision,
3558
+ now?: Date | string
3559
+ ): AssistantAnswerActionabilityContract {
3560
+ const status = assistantActionabilityStatus(decision);
3561
+ const primaryCommand = assistantActionabilityCommand(status);
3562
+ const ready = status === 'ready_to_answer';
3563
+ const noDataReady = ready && decision.queryStatus === 'no_data' && decision.noDataConfirmed;
3564
+ const canAnswerCustomer = ready && (
3565
+ decision.queryStatus === 'ok'
3566
+ || noDataReady
3567
+ || decision.queryEvidenceRequired === false
3568
+ );
3569
+ const canDraftSupportReply = canAnswerCustomer && decision.confidenceLevel === 'high';
3570
+ const requiresHumanReview = !canAnswerCustomer || canDraftSupportReply;
3571
+ const cheapRepair = /needs_(?:no_data|date_window|query_shape|next_action)|needs_evidence/.test(status);
3572
+ const blockers = decision.blockers.length
3573
+ ? decision.blockers
3574
+ : (ready ? [] : [decision.reason]);
3575
+ const requiredEvidence = Array.from(new Set([
3576
+ ...(decision.queryEvidenceRequired ? ['structured query/tool execution proof'] : []),
3577
+ ...(decision.dateWindowRequired ? ['concrete date-window start and end'] : []),
3578
+ ...(decision.queryStatus === 'no_data' ? ['noDataConfirmed=true from scoped probe or query evidence'] : []),
3579
+ ...(decision.legalQueryShape ? [] : ['legal Mongo query/projection shape']),
3580
+ ...(decision.citationRefs.length || decision.evidenceRefs.length ? [] : ['citationRefs or evidenceRefs']),
3581
+ ...(decision.nextActions.length ? [] : ['one explicit next action or no-action state']),
3582
+ ...(decision.confidenceLevel === 'low' || decision.confidenceLevel === 'unknown' ? ['medium/high confidence evidence'] : [])
3583
+ ])).slice(0, 20);
3584
+ return {
3585
+ contractId: `assistant-actionability:${decision.status}:${decision.queryStatus}:${decision.recordedAt}`,
3586
+ status,
3587
+ primaryCommand,
3588
+ label: assistantActionabilityLabel(status),
3589
+ canAnswerCustomer,
3590
+ canDraftSupportReply,
3591
+ canSendCustomerReply: false,
3592
+ requiresHumanReview,
3593
+ canRunWithoutCodexMonitor: ready || cheapRepair,
3594
+ codexFallbackRequired: !ready && !cheapRepair,
3595
+ costRisk: ready
3596
+ ? 'free_or_deterministic'
3597
+ : status === 'needs_permission'
3598
+ ? 'manual_blocked'
3599
+ : (status === 'needs_query_repair' || status === 'incorrect' ? 'small_model_or_qa' : 'free_or_deterministic'),
3600
+ queryResultClass: decision.queryStatus,
3601
+ confidenceLevel: decision.confidenceLevel,
3602
+ decisionBasis: {
3603
+ answerQualityStatus: decision.status,
3604
+ queryStatus: decision.queryStatus,
3605
+ queryEvidencePresent: decision.queryEvidencePresent,
3606
+ noDataConfirmed: decision.noDataConfirmed,
3607
+ legalQueryShape: decision.legalQueryShape,
3608
+ dateWindowRequired: decision.dateWindowRequired,
3609
+ dateWindowPresent: decision.dateWindowPresent,
3610
+ citationCount: decision.citationRefs.length + decision.evidenceRefs.length,
3611
+ nextActionCount: decision.nextActions.length
3612
+ },
3613
+ requiredEvidence,
3614
+ successEvidence: Array.from(new Set([
3615
+ ...(ready ? ['assistant answer quality gate passed'] : []),
3616
+ ...(decision.queryEvidenceRefs.length ? decision.queryEvidenceRefs : []),
3617
+ ...(decision.citationRefs.length ? decision.citationRefs : []),
3618
+ ...(decision.evidenceRefs.length ? decision.evidenceRefs : [])
3619
+ ])).slice(0, 20),
3620
+ blockers,
3621
+ nextActions: decision.nextActions,
3622
+ nextCommands: Array.from(new Set([
3623
+ primaryCommand,
3624
+ ...(status === 'needs_query_repair' ? ['fix_query_error', 'rerun_assistant_query'] : []),
3625
+ ...(status === 'needs_no_data_verification' ? ['run_tiny_shape_probe', 'record_no_data_confirmed'] : []),
3626
+ ...(status === 'needs_date_window_repair' ? ['resolve_current_date', 'record_concrete_date_window', 'rerun_assistant_query'] : []),
3627
+ ...(status === 'needs_query_shape_repair' ? ['rewrite_illegal_projection', 'rerun_query_shape_validator'] : []),
3628
+ ...(status === 'needs_permission' ? ['request_permission_or_choose_allowed_route'] : []),
3629
+ 'record_assistant_actionability_result'
3630
+ ])).slice(0, 20),
3631
+ forbiddenActions: Array.from(new Set([
3632
+ 'Do not answer from keyword or route guesses without verified query evidence.',
3633
+ 'Do not claim no data when queryStatus is query_error or permission_error.',
3634
+ ...(decision.queryStatus === 'no_data' && !decision.noDataConfirmed ? ['Do not present no-data answer until noDataConfirmed=true.'] : []),
3635
+ ...(decision.legalQueryShape ? [] : ['Do not answer from illegal Mongo projection/query shape.']),
3636
+ ...(decision.dateWindowRequired && !decision.dateWindowPresent ? ['Do not answer dated questions without concrete start/end date evidence.'] : []),
3637
+ ...(decision.confidenceLevel === 'low' || decision.confidenceLevel === 'unknown' ? ['Do not draft support/customer reply from low or unknown confidence.'] : [])
3638
+ ])).slice(0, 20),
3639
+ evidenceRefs: Array.from(new Set([
3640
+ ...decision.queryEvidenceRefs,
3641
+ ...decision.citationRefs,
3642
+ ...decision.evidenceRefs
3643
+ ])).slice(0, 40),
3644
+ recordedAt: isoNow(now || decision.recordedAt)
3645
+ };
3646
+ }
3647
+
3648
+ function assistantAnswerActionabilityGate(
3649
+ contract: AssistantAnswerActionabilityContract,
3650
+ now?: Date | string
3651
+ ): AIRunGateResult {
3652
+ const status: AIRunGateResult['status'] = contract.status === 'ready_to_answer'
3653
+ ? 'pass'
3654
+ : contract.status === 'incorrect' || contract.status === 'needs_query_shape_repair' || contract.status === 'needs_date_window_repair'
3655
+ ? 'fail'
3656
+ : 'blocked';
3657
+ return {
3658
+ key: 'assistant_answer_actionability',
3659
+ label: 'Assistant answer actionability',
3660
+ status,
3661
+ reason: status === 'pass'
3662
+ ? 'Assistant answer can proceed from verified structured evidence.'
3663
+ : (contract.blockers.join(' ') || 'Assistant answer is blocked until actionability evidence is complete.'),
3664
+ evidenceRefs: contract.evidenceRefs,
3665
+ recordedAt: isoNow(now || contract.recordedAt),
3666
+ metadata: {
3667
+ contractId: contract.contractId,
3668
+ status: contract.status,
3669
+ primaryCommand: contract.primaryCommand,
3670
+ label: contract.label,
3671
+ canAnswerCustomer: contract.canAnswerCustomer,
3672
+ canDraftSupportReply: contract.canDraftSupportReply,
3673
+ canSendCustomerReply: contract.canSendCustomerReply,
3674
+ requiresHumanReview: contract.requiresHumanReview,
3675
+ canRunWithoutCodexMonitor: contract.canRunWithoutCodexMonitor,
3676
+ codexFallbackRequired: contract.codexFallbackRequired,
3677
+ costRisk: contract.costRisk,
3678
+ queryResultClass: contract.queryResultClass,
3679
+ confidenceLevel: contract.confidenceLevel,
3680
+ decisionBasis: contract.decisionBasis,
3681
+ requiredEvidence: contract.requiredEvidence,
3682
+ successEvidence: contract.successEvidence,
3683
+ blockers: contract.blockers,
3684
+ nextActions: contract.nextActions,
3685
+ nextCommands: contract.nextCommands,
3686
+ forbiddenActions: contract.forbiddenActions
3687
+ }
3688
+ };
3689
+ }
3690
+
3691
+ function applyAssistantAnswerQualityGate(
3692
+ qa: AIQaRun,
3693
+ decision: AssistantAnswerQualityDecision,
3694
+ now?: Date | string
3695
+ ): AIQaRun {
3696
+ const gate = assistantAnswerQualityGate(decision, now);
3697
+ if (decision.ready) {
3698
+ return {
3699
+ ...qa,
3700
+ gateResults: [...qa.gateResults, gate]
3701
+ };
3702
+ }
3703
+ return {
3704
+ ...qa,
3705
+ outcome: /incorrect|date_incorrect|missing_date_window|illegal_query_shape/.test(decision.status)
3706
+ ? 'business_assertion_failed'
3707
+ : 'incomplete',
3708
+ gateResults: [
3709
+ ...qa.gateResults.filter((existing) => !(existing.key === 'qa_business_assertion' && existing.status === 'pass')),
3710
+ gate
3711
+ ]
3712
+ };
3713
+ }
3714
+
3715
+ export function buildSupportAIRunFromEvidence(input: SupportAIRunAdapterInput): AIRun {
3716
+ const ticket = input.ticket || {};
3717
+ const job = input.job || {};
3718
+ const evidence = buildSupportQaEvidence(input);
3719
+ const events: AIRunEvent[] = [];
3720
+ const gates: AIRunGateResult[] = [];
3721
+ const sourceIds: Record<string, string> = {};
3722
+ addSourceId(sourceIds, 'ticketId', ticket._id || ticket.id || job.ticketId || job.supportTicketId);
3723
+ addSourceId(sourceIds, 'ticketNumber', ticket.ticketNumber || ticket.number || job.ticketNumber);
3724
+ addSourceId(sourceIds, 'jobId', job._id || job.id || job.jobId);
3725
+ addSourceId(sourceIds, 'buildPlanId', job.buildPlanId || ticket.buildPlanId);
3726
+
3727
+ for (const event of asArray<Record<string, any>>(input.taskEvents)) {
3728
+ pushEvent(events, {
3729
+ type: event.type === 'human_intervention' ? 'human_intervention' : 'log',
3730
+ category: cleanText(event.category || event.phase || event.type, 160),
3731
+ message: cleanText(event.message || event.summary || event.text || event.title, 1800),
3732
+ recordedAt: eventDate(event),
3733
+ metadata: {
3734
+ status: event.status,
3735
+ phase: event.phase,
3736
+ taskId: event.taskId || event._id || event.id
3737
+ }
3738
+ });
3739
+ }
3740
+ for (const aiJob of asArray<Record<string, any>>(input.aiJobs)) {
3741
+ pushEvent(events, {
3742
+ type: aiJob.model ? 'model_call' : 'log',
3743
+ category: cleanText(aiJob.category || aiJob.phase || aiJob.runner, 160),
3744
+ message: cleanText(aiJob.summary || aiJob.promptSummary || aiJob.status || aiJob.phase, 1800),
3745
+ recordedAt: eventDate(aiJob),
3746
+ metadata: {
3747
+ id: aiJob._id || aiJob.id,
3748
+ model: aiJob.model,
3749
+ status: aiJob.status,
3750
+ phase: aiJob.phase
3751
+ }
3752
+ });
3753
+ }
3754
+ collectCommitEvents(asArray<Record<string, any>>(input.commits), events);
3755
+ collectHotfixEvidenceEvents([job, ticket, evidence, input.qaEvidence], events, gates, input.now);
3756
+ const noBlindLoopGate = supportManagerNoBlindLoopGate(job, evidence, input.now);
3757
+ if (noBlindLoopGate) {
3758
+ gates.push(noBlindLoopGate);
3759
+ pushEvent(events, {
3760
+ type: 'log',
3761
+ category: 'manager_no_blind_loop_policy',
3762
+ message: noBlindLoopGate.reason,
3763
+ artifactPaths: noBlindLoopGate.evidenceRefs,
3764
+ recordedAt: noBlindLoopGate.recordedAt,
3765
+ metadata: noBlindLoopGate.metadata
3766
+ });
3767
+ }
3768
+ const businessProofReadiness = plainObject(evidence.businessProofReadiness || evidence.business_proof_readiness);
3769
+ const businessProofReadinessGate = supportBusinessProofReadinessGate(businessProofReadiness, input.now);
3770
+ if (businessProofReadinessGate) {
3771
+ gates.push(businessProofReadinessGate);
3772
+ pushEvent(events, {
3773
+ type: 'qa_business_assertion',
3774
+ category: 'support_business_proof_readiness',
3775
+ message: businessProofReadinessGate.reason,
3776
+ artifactPaths: businessProofReadinessGate.evidenceRefs,
3777
+ recordedAt: businessProofReadinessGate.recordedAt,
3778
+ metadata: businessProofReadinessGate.metadata
3779
+ });
3780
+ }
3781
+ const customerReplyPolicy = plainObject(evidence.customerReplyPolicy || evidence.customer_reply_policy);
3782
+ const customerReplyPolicyGate = supportCustomerReplyPolicyGate(customerReplyPolicy, input.now);
3783
+ if (customerReplyPolicyGate) {
3784
+ gates.push(customerReplyPolicyGate);
3785
+ pushEvent(events, {
3786
+ type: 'human_intervention',
3787
+ category: 'support_customer_reply_policy',
3788
+ message: customerReplyPolicyGate.reason,
3789
+ artifactPaths: customerReplyPolicyGate.evidenceRefs,
3790
+ recordedAt: customerReplyPolicyGate.recordedAt,
3791
+ metadata: customerReplyPolicyGate.metadata
3792
+ });
3793
+ }
3794
+ const customerReplyReadiness = plainObject(
3795
+ evidence.customerReplyReadiness
3796
+ || evidence.customer_reply_readiness
3797
+ || customerReplyPolicy.readinessContract
3798
+ || customerReplyPolicy.readiness_contract
3799
+ );
3800
+ const customerReplyReadinessGate = supportCustomerReplyReadinessGate(customerReplyReadiness, input.now);
3801
+ if (customerReplyReadinessGate) {
3802
+ gates.push(customerReplyReadinessGate);
3803
+ pushEvent(events, {
3804
+ type: 'human_intervention',
3805
+ category: 'support_customer_reply_readiness',
3806
+ message: customerReplyReadinessGate.reason,
3807
+ artifactPaths: customerReplyReadinessGate.evidenceRefs,
3808
+ recordedAt: customerReplyReadinessGate.recordedAt,
3809
+ metadata: customerReplyReadinessGate.metadata
3810
+ });
3811
+ }
3812
+ const nextActionContract = supportNextActionContractObject(ticket, job, evidence);
3813
+ const nextActionContractRequired = supportNextActionContractRequired(ticket, job, evidence);
3814
+ const nextActionContractGate = supportNextActionContractGate(nextActionContract, input.now, nextActionContractRequired);
3815
+ if (nextActionContractGate) {
3816
+ gates.push(nextActionContractGate);
3817
+ pushEvent(events, {
3818
+ type: 'log',
3819
+ category: 'support_next_action_contract',
3820
+ message: nextActionContractGate.reason,
3821
+ artifactPaths: nextActionContractGate.evidenceRefs,
3822
+ recordedAt: nextActionContractGate.recordedAt,
3823
+ metadata: nextActionContractGate.metadata
3824
+ });
3825
+ }
3826
+ const managerRecoveryExecutionProof = plainObject(evidence.managerRecoveryExecutionProof || evidence.manager_recovery_execution_proof);
3827
+ const recoveryExecutionProofGate = managerRecoveryExecutionProofGate(managerRecoveryExecutionProof, input.now);
3828
+ if (recoveryExecutionProofGate) {
3829
+ gates.push(recoveryExecutionProofGate);
3830
+ pushEvent(events, {
3831
+ type: 'log',
3832
+ category: 'manager_recovery_execution_proof',
3833
+ message: recoveryExecutionProofGate.reason,
3834
+ artifactPaths: recoveryExecutionProofGate.evidenceRefs,
3835
+ recordedAt: recoveryExecutionProofGate.recordedAt,
3836
+ metadata: recoveryExecutionProofGate.metadata
3837
+ });
3838
+ }
3839
+ const ticketAutomation = plainObject(ticket.automation);
3840
+ const ticketManagerState = plainObject(ticketAutomation.manager || ticket.manager);
3841
+ const rootCauseEntryContract = evidenceObject(
3842
+ job.supportRootCauseEntryContract,
3843
+ job.support_root_cause_entry_contract,
3844
+ job.rootCauseEntryContract,
3845
+ ticket.supportRootCauseEntryContract,
3846
+ ticket.rootCauseEntryContract,
3847
+ ticketManagerState.root_cause_entry_contract,
3848
+ ticketManagerState.rootCauseEntryContract,
3849
+ evidence.rootCauseEntryContract,
3850
+ evidence.root_cause_entry_contract
3851
+ );
3852
+ const rootCauseEntryContractGate = supportRootCauseEntryContractGate(rootCauseEntryContract, input.now);
3853
+ if (rootCauseEntryContractGate) {
3854
+ gates.push(rootCauseEntryContractGate);
3855
+ pushEvent(events, {
3856
+ type: 'log',
3857
+ category: 'support_root_cause_entry_contract',
3858
+ message: rootCauseEntryContractGate.reason,
3859
+ artifactPaths: rootCauseEntryContractGate.evidenceRefs,
3860
+ recordedAt: rootCauseEntryContractGate.recordedAt,
3861
+ metadata: rootCauseEntryContractGate.metadata
3862
+ });
3863
+ }
3864
+ const cost = collectUsageEvents(asArray<Record<string, any>>(input.usageLedger), events);
3865
+ const costGate = costObservabilityGate(cost, 'support_ticket', input.now);
3866
+ gates.push(costGate);
3867
+ pushEvent(events, {
3868
+ type: 'usage',
3869
+ category: 'cost_observability',
3870
+ message: costGate.reason,
3871
+ artifactPaths: costGate.evidenceRefs,
3872
+ recordedAt: costGate.recordedAt,
3873
+ metadata: costGate.metadata
3874
+ });
3875
+ const preflightGateSource = supportPreflightGateObject(ticket, job, evidence);
3876
+ const preflightGate = supportPreflightGateResult(preflightGateSource, input.now);
3877
+ if (preflightGate) {
3878
+ gates.push(preflightGate);
3879
+ pushEvent(events, {
3880
+ type: preflightGate.metadata?.failureClass === 'compile' || preflightGateSource.status === 'compile_failed' ? 'qa_compile' : 'qa_infra',
3881
+ category: 'support_preflight_gate',
3882
+ message: preflightGate.reason,
3883
+ artifactPaths: preflightGate.evidenceRefs,
3884
+ recordedAt: preflightGate.recordedAt,
3885
+ metadata: preflightGate.metadata
3886
+ });
3887
+ }
3888
+
3889
+ const diagnosisGate = evidenceObject(
3890
+ job.supportV5DiagnosisGate,
3891
+ job.support_v5_diagnosis_gate,
3892
+ job.diagnosisGate,
3893
+ ticket.supportV5DiagnosisGate,
3894
+ ticket.diagnosisGate,
3895
+ evidence.diagnosisGate,
3896
+ evidence.supportV5DiagnosisGate
3897
+ );
3898
+ const diagnosisEvidencePackSource = supportDiagnosisEvidencePackObject(ticket, job, evidence);
3899
+ const similarFixHints = supportSimilarFixHintsObject(ticket, job, evidence);
3900
+ const diagnosisEvidencePack = Object.keys(diagnosisEvidencePackSource).length
3901
+ ? diagnosisEvidencePackSource
3902
+ : (Object.keys(diagnosisGate).length || Object.keys(similarFixHints).length)
3903
+ ? buildResolveIOSupportDiagnosisEvidencePack({
3904
+ diagnosisGate,
3905
+ similarCaseHints: similarFixHints,
3906
+ issueClass: diagnosisGate.issue_class || diagnosisGate.issueClass,
3907
+ ownerFiles: diagnosisGate.owner_files || diagnosisGate.ownerFiles,
3908
+ text: [
3909
+ ticket.title,
3910
+ ticket.subject,
3911
+ ticket.summary,
3912
+ job.title,
3913
+ job.summary
3914
+ ].filter(Boolean).join(' '),
3915
+ now: input.now
3916
+ }) as Record<string, any>
3917
+ : {};
3918
+ const diagnosisEvidencePackGate = supportDiagnosisEvidencePackGate(diagnosisEvidencePack, input.now);
3919
+ if (diagnosisEvidencePackGate) {
3920
+ gates.push(diagnosisEvidencePackGate);
3921
+ pushEvent(events, {
3922
+ type: 'log',
3923
+ category: 'support_diagnosis_evidence_pack',
3924
+ message: diagnosisEvidencePackGate.reason,
3925
+ artifactPaths: diagnosisEvidencePackGate.evidenceRefs,
3926
+ recordedAt: diagnosisEvidencePackGate.recordedAt,
3927
+ metadata: diagnosisEvidencePackGate.metadata
3928
+ });
3929
+ }
3930
+ const issueClassProbePlanSource = supportIssueClassProbePlanObject(ticket, job, evidence);
3931
+ const issueClassProbePlan = Object.keys(issueClassProbePlanSource).length
3932
+ ? issueClassProbePlanSource
3933
+ : Object.keys(diagnosisGate).length
3934
+ ? buildResolveIOSupportIssueClassProbePlan(diagnosisGate, input.now) as Record<string, any>
3935
+ : {};
3936
+ const issueClassProbePlanRequired = nextActionContractRequired || Object.keys(diagnosisGate).length > 0;
3937
+ const issueClassProbePlanGate = supportIssueClassProbePlanGate(
3938
+ issueClassProbePlan,
3939
+ diagnosisGate,
3940
+ input.now,
3941
+ issueClassProbePlanRequired
3942
+ );
3943
+ if (issueClassProbePlanGate) {
3944
+ gates.push(issueClassProbePlanGate);
3945
+ pushEvent(events, {
3946
+ type: 'log',
3947
+ category: 'support_issue_class_probe_plan',
3948
+ message: issueClassProbePlanGate.reason,
3949
+ artifactPaths: issueClassProbePlanGate.evidenceRefs,
3950
+ recordedAt: issueClassProbePlanGate.recordedAt,
3951
+ metadata: issueClassProbePlanGate.metadata
3952
+ });
3953
+ }
3954
+ const productRepairEvidence = supportProductRepairEvidence(input, evidence);
3955
+ const diagnosisBeforeRepairGate = supportDiagnosisBeforeRepairGate(diagnosisGate, productRepairEvidence, input.now);
3956
+ if (diagnosisBeforeRepairGate) {
3957
+ gates.push(diagnosisBeforeRepairGate);
3958
+ pushEvent(events, {
3959
+ type: 'log',
3960
+ category: 'support_diagnosis_before_repair',
3961
+ message: diagnosisBeforeRepairGate.reason,
3962
+ artifactPaths: diagnosisBeforeRepairGate.evidenceRefs,
3963
+ recordedAt: diagnosisBeforeRepairGate.recordedAt,
3964
+ metadata: diagnosisBeforeRepairGate.metadata
3965
+ });
3966
+ }
3967
+ const ownerFileScopeGate = supportOwnerFileScopeGate(diagnosisGate, productRepairEvidence, input.now);
3968
+ if (ownerFileScopeGate) {
3969
+ gates.push(ownerFileScopeGate);
3970
+ pushEvent(events, {
3971
+ type: 'log',
3972
+ category: 'support_owner_file_scope',
3973
+ message: ownerFileScopeGate.reason,
3974
+ artifactPaths: ownerFileScopeGate.evidenceRefs,
3975
+ recordedAt: ownerFileScopeGate.recordedAt,
3976
+ metadata: ownerFileScopeGate.metadata
3977
+ });
3978
+ }
3979
+ const qa = applySupportDiagnosisProofGate(buildQaFromEvidence(evidence, input.now), diagnosisGate, input.now);
3980
+ const diagnosisOwnerFiles = asArray<string>(diagnosisGate.owner_files || diagnosisGate.ownerFiles);
3981
+ const diagnosisIssueClass = cleanText(diagnosisGate.issue_class || diagnosisGate.issueClass, 120);
3982
+ if (Object.keys(diagnosisGate).length) {
3983
+ pushEvent(events, {
3984
+ type: 'log',
3985
+ category: 'diagnosis_gate',
3986
+ message: cleanText(diagnosisGate.accepted_hypothesis?.statement || diagnosisGate.acceptedHypothesis?.statement || diagnosisGate.summary || 'Support diagnosis gate recorded.', 1800),
3987
+ metadata: {
3988
+ status: diagnosisGate.status,
3989
+ issueClass: diagnosisIssueClass,
3990
+ ownerFiles: diagnosisOwnerFiles
3991
+ }
3992
+ });
3993
+ }
3994
+ return buildAIRun({
3995
+ source: 'support_ticket',
3996
+ sourceIds,
3997
+ title: firstText(ticket, ['title', 'subject', 'summary']) || firstText(job, ['title', 'summary']),
3998
+ status: firstText(job, ['status', 'state']) || firstText(ticket, ['status', 'state']),
3999
+ phase: firstText(job, ['phase', 'runnerPhase']),
4000
+ startedAt: dateValue(job, ['startedAt', 'createdAt']) || dateValue(ticket, ['createdAt']),
4001
+ completedAt: dateValue(job, ['completedAt', 'finishedAt', 'updatedAt']),
4002
+ events,
4003
+ gates,
4004
+ qa,
4005
+ cost,
4006
+ scorecardPassed: evidence.scorecardPassed === true || evidence.scorecardStatus === 'pass',
4007
+ deployStatus: evidence.deployStatus,
4008
+ publishStatus: evidence.publishStatus,
4009
+ sampleDataStatus: evidence.sampleDataStatus,
4010
+ manualHandoff: evidence.manualHandoff === true || job.manualHandoff === true,
4011
+ stopped: evidence.stopped === true || job.stopped === true,
4012
+ rejected: evidence.rejected === true,
4013
+ explicitAccepted: evidence.explicitAccepted === true || ticket.accepted === true,
4014
+ terminal: /complete|closed|resolved|done|failed|rejected/i.test(`${job.status || ''} ${ticket.status || ''}`),
4015
+ now: input.now,
4016
+ metadata: {
4017
+ versionCount: asArray(input.versions).length,
4018
+ buildPlanCount: asArray(input.buildPlans).length,
4019
+ costObservability: costObservabilityMetadata(costGate),
4020
+ hotfixDurability: hotfixDurabilityMetadata(gates),
4021
+ managerNoBlindLoopPolicy: managerNoBlindLoopMetadata(noBlindLoopGate),
4022
+ diagnosisBeforeRepair: diagnosisBeforeRepairGate ? {
4023
+ status: diagnosisBeforeRepairGate.status,
4024
+ diagnosisValid: diagnosisBeforeRepairGate.metadata?.diagnosisValid === true,
4025
+ diagnosisStatus: cleanText(diagnosisBeforeRepairGate.metadata?.diagnosisStatus, 120),
4026
+ activityCount: Number(diagnosisBeforeRepairGate.metadata?.activityCount || 0),
4027
+ changedFiles: cleanStringList(diagnosisBeforeRepairGate.metadata?.changedFiles, 40, 500),
4028
+ allowedDispatchAction: cleanText(diagnosisBeforeRepairGate.metadata?.allowedDispatchAction, 160),
4029
+ productRepairAllowed: diagnosisBeforeRepairGate.metadata?.productRepairAllowed === true,
4030
+ blockers: cleanStringList(diagnosisBeforeRepairGate.metadata?.blockers, 20, 500)
4031
+ } : undefined,
4032
+ ownerFileScope: ownerFileScopeGate ? {
4033
+ status: ownerFileScopeGate.status,
4034
+ productRepairAllowed: ownerFileScopeGate.metadata?.productRepairAllowed === true,
4035
+ allowedDispatchAction: cleanText(ownerFileScopeGate.metadata?.allowedDispatchAction, 160),
4036
+ ownerFiles: cleanStringList(ownerFileScopeGate.metadata?.ownerFiles, 40, 500),
4037
+ changedFiles: cleanStringList(ownerFileScopeGate.metadata?.changedFiles, 60, 500),
4038
+ outsideOwnerFiles: cleanStringList(ownerFileScopeGate.metadata?.outsideOwnerFiles, 40, 500),
4039
+ allowedTestFilesOutsideOwnerScope: ownerFileScopeGate.metadata?.allowedTestFilesOutsideOwnerScope === true,
4040
+ requiredEvidence: cleanStringList(ownerFileScopeGate.metadata?.requiredEvidence, 20, 500)
4041
+ } : undefined,
4042
+ rootCauseEntryContract: Object.keys(rootCauseEntryContract).length ? {
4043
+ contractId: cleanText(rootCauseEntryContract.contract_id || rootCauseEntryContract.contractId, 160),
4044
+ version: cleanText(rootCauseEntryContract.version, 120),
4045
+ status: cleanText(rootCauseEntryContract.status, 120),
4046
+ issueClassProbeCount: asArray(rootCauseEntryContract.issue_class_probes || rootCauseEntryContract.issueClassProbes).length,
4047
+ ownerFileMax: Number(rootCauseEntryContract.owner_file_policy?.max_files || rootCauseEntryContract.ownerFilePolicy?.maxFiles || 0) || 0,
4048
+ requiresBusinessAssertion: rootCauseEntryContract.business_proof_policy?.requires_aiqa_business_assertion === true
4049
+ || rootCauseEntryContract.businessProofPolicy?.requiresAiqaBusinessAssertion === true
4050
+ } : undefined,
4051
+ diagnosis: Object.keys(diagnosisGate).length ? {
4052
+ status: cleanText(diagnosisGate.status, 80),
4053
+ issueClass: diagnosisIssueClass,
4054
+ ownerFiles: diagnosisOwnerFiles,
4055
+ acceptedHypothesis: cleanText(diagnosisGate.accepted_hypothesis?.statement || diagnosisGate.acceptedHypothesis?.statement, 1000),
4056
+ proofPlan: diagnosisGate.proof_plan || diagnosisGate.proofPlan
4057
+ } : undefined,
4058
+ diagnosisEvidencePack: diagnosisEvidencePackGate ? {
4059
+ status: cleanText(diagnosisEvidencePackGate.metadata?.status, 120),
4060
+ gateStatus: diagnosisEvidencePackGate.status,
4061
+ packId: cleanText(diagnosisEvidencePackGate.metadata?.packId, 180),
4062
+ readOnly: diagnosisEvidencePackGate.metadata?.readOnly !== false,
4063
+ sourceEditsAllowed: diagnosisEvidencePackGate.metadata?.sourceEditsAllowed === true,
4064
+ requiredOutputKey: cleanText(diagnosisEvidencePackGate.metadata?.requiredOutputKey, 120),
4065
+ diagnosisValid: diagnosisEvidencePackGate.metadata?.diagnosisValid === true,
4066
+ issueClassHint: cleanText(diagnosisEvidencePackGate.metadata?.issueClassHint, 120),
4067
+ ownerFileHints: cleanStringList(diagnosisEvidencePackGate.metadata?.ownerFileHints, 20, 500),
4068
+ similarHintCount: Number(diagnosisEvidencePackGate.metadata?.similarHintCount || 0),
4069
+ similarHintsAdvisoryOnly: diagnosisEvidencePackGate.metadata?.similarHintsAdvisoryOnly === true,
4070
+ issueClassProbeCount: Number(diagnosisEvidencePackGate.metadata?.issueClassProbeCount || 0),
4071
+ validationBlockers: cleanStringList(diagnosisEvidencePackGate.metadata?.validationBlockers, 20, 500)
4072
+ } : undefined,
4073
+ issueClassProbePlan: issueClassProbePlanGate ? {
4074
+ status: cleanText(issueClassProbePlanGate.metadata?.status, 120),
4075
+ gateStatus: issueClassProbePlanGate.status,
4076
+ planId: cleanText(issueClassProbePlanGate.metadata?.planId, 180),
4077
+ issueClass: cleanText(issueClassProbePlanGate.metadata?.issueClass, 120),
4078
+ diagnosisValid: issueClassProbePlanGate.metadata?.diagnosisValid === true,
4079
+ issueClassProbePlanReady: issueClassProbePlanGate.metadata?.issueClassProbePlanReady === true,
4080
+ activeRoute: cleanText(issueClassProbePlanGate.metadata?.activeRoute, 500),
4081
+ action: cleanText(issueClassProbePlanGate.metadata?.action, 1000),
4082
+ expectedBusinessProof: cleanText(issueClassProbePlanGate.metadata?.expectedBusinessProof, 1200),
4083
+ acceptanceGate: cleanText(issueClassProbePlanGate.metadata?.acceptanceGate, 120),
4084
+ requiredArtifacts: cleanStringList(issueClassProbePlanGate.metadata?.requiredArtifacts, 20, 500),
4085
+ falsePassBlockers: cleanStringList(issueClassProbePlanGate.metadata?.falsePassBlockers, 20, 500),
4086
+ blockers: cleanStringList(issueClassProbePlanGate.metadata?.blockers, 20, 500)
4087
+ } : undefined,
4088
+ businessProofReadiness: Object.keys(businessProofReadiness).length ? {
4089
+ ready: businessProofReadiness.ready === true,
4090
+ status: cleanText(businessProofReadiness.status, 120),
4091
+ reason: cleanText(businessProofReadiness.reason, 1000),
4092
+ blockers: cleanStringList(businessProofReadiness.blockers, 20, 500)
4093
+ } : undefined,
4094
+ nextActionContract: nextActionContractGate ? {
4095
+ status: nextActionContractGate.status,
4096
+ action: cleanText(nextActionContractGate.metadata?.action, 160),
4097
+ primaryCommand: cleanText(nextActionContractGate.metadata?.primaryCommand, 240),
4098
+ safeToAutoRun: nextActionContractGate.metadata?.safeToAutoRun === true,
4099
+ canRunWithoutCodexMonitor: nextActionContractGate.metadata?.canRunWithoutCodexMonitor === true,
4100
+ codexFallbackRequired: nextActionContractGate.metadata?.codexFallbackRequired === true,
4101
+ requiresHumanApproval: nextActionContractGate.metadata?.requiresHumanApproval === true,
4102
+ rootCauseFirstSatisfied: nextActionContractGate.metadata?.rootCauseFirstSatisfied === true,
4103
+ hotfixCommitRequired: nextActionContractGate.metadata?.hotfixCommitRequired === true,
4104
+ liveHotfixBlockedUntilCommit: nextActionContractGate.metadata?.liveHotfixBlockedUntilCommit === true,
4105
+ failureClass: cleanText(nextActionContractGate.metadata?.failureClass, 120),
4106
+ evidenceFreshnessStatus: cleanText(nextActionContractGate.metadata?.evidenceFreshnessStatus, 120),
4107
+ sameFailureCount: Number(nextActionContractGate.metadata?.sameFailureCount || 0),
4108
+ blockers: cleanStringList(nextActionContractGate.metadata?.blockers, 20, 500)
4109
+ } : undefined,
4110
+ managerRecoveryExecutionProof: recoveryExecutionProofGate ? {
4111
+ status: cleanText(recoveryExecutionProofGate.metadata?.status, 120),
4112
+ gateStatus: recoveryExecutionProofGate.status,
4113
+ canContinueRun: recoveryExecutionProofGate.metadata?.canContinueRun === true,
4114
+ canRunProductRepair: recoveryExecutionProofGate.metadata?.canRunProductRepair === true,
4115
+ canRunExpensiveModel: recoveryExecutionProofGate.metadata?.canRunExpensiveModel === true,
4116
+ canResetLoopBudget: recoveryExecutionProofGate.metadata?.canResetLoopBudget === true,
4117
+ newEvidence: recoveryExecutionProofGate.metadata?.newEvidence === true,
4118
+ materialEvidence: recoveryExecutionProofGate.metadata?.materialEvidence === true,
4119
+ evidenceStrength: cleanText(recoveryExecutionProofGate.metadata?.evidenceStrength, 120),
4120
+ nextAllowedAction: cleanText(recoveryExecutionProofGate.metadata?.nextAllowedAction, 180),
4121
+ missingEvidence: cleanStringList(recoveryExecutionProofGate.metadata?.missingEvidence, 20, 500),
4122
+ blockers: cleanStringList(recoveryExecutionProofGate.metadata?.blockers, 20, 500)
4123
+ } : undefined,
4124
+ customerReplyPolicy: Object.keys(customerReplyPolicy).length ? {
4125
+ action: cleanText(customerReplyPolicy.action, 120),
4126
+ safety: cleanText(customerReplyPolicy.safety, 120),
4127
+ canDraftCustomerReply: customerReplyPolicy.canDraftCustomerReply === true || customerReplyPolicy.can_draft_customer_reply === true,
4128
+ canSendCustomerReply: customerReplyPolicy.canSendCustomerReply === true || customerReplyPolicy.can_send_customer_reply === true,
4129
+ reason: cleanText(customerReplyPolicy.reason, 1000),
4130
+ reviewType: cleanText(
4131
+ customerReplyPolicy.humanReviewPacket?.reviewType
4132
+ || customerReplyPolicy.human_review_packet?.review_type
4133
+ || customerReplyPolicy.human_review_packet?.reviewType,
4134
+ 160
4135
+ ),
4136
+ primaryAction: cleanText(
4137
+ customerReplyPolicy.humanReviewPacket?.primaryAction
4138
+ || customerReplyPolicy.human_review_packet?.primary_action
4139
+ || customerReplyPolicy.human_review_packet?.primaryAction,
4140
+ 160
4141
+ ),
4142
+ clarificationContract: customerReplyPolicy.clarificationContract || customerReplyPolicy.clarification_contract ? {
4143
+ status: cleanText(customerReplyPolicy.clarificationContract?.status || customerReplyPolicy.clarification_contract?.status, 120),
4144
+ missingField: cleanText(customerReplyPolicy.clarificationContract?.missingField || customerReplyPolicy.clarification_contract?.missing_field, 120),
4145
+ oneQuestionOnly: customerReplyPolicy.clarificationContract?.oneQuestionOnly === true || customerReplyPolicy.clarification_contract?.one_question_only === true,
4146
+ parksTicketUntilCustomerReply: customerReplyPolicy.clarificationContract?.parksTicketUntilCustomerReply === true || customerReplyPolicy.clarification_contract?.parks_ticket_until_customer_reply === true
4147
+ } : undefined
4148
+ } : undefined,
4149
+ customerReplyReadiness: customerReplyReadinessGate ? {
4150
+ status: cleanText(customerReplyReadinessGate.metadata?.status, 120),
4151
+ gateStatus: customerReplyReadinessGate.status,
4152
+ primaryCommand: cleanText(customerReplyReadinessGate.metadata?.primaryCommand, 180),
4153
+ policyAction: cleanText(customerReplyReadinessGate.metadata?.policyAction, 120),
4154
+ canDraftCustomerReply: customerReplyReadinessGate.metadata?.canDraftCustomerReply === true,
4155
+ canSendCustomerReply: customerReplyReadinessGate.metadata?.canSendCustomerReply === true,
4156
+ requiresHumanApproval: customerReplyReadinessGate.metadata?.requiresHumanApproval === true,
4157
+ proofBacked: customerReplyReadinessGate.metadata?.proofBacked === true,
4158
+ businessProofReady: customerReplyReadinessGate.metadata?.businessProofReady === true,
4159
+ releaseReady: customerReplyReadinessGate.metadata?.releaseReady === true,
4160
+ blockers: cleanStringList(customerReplyReadinessGate.metadata?.blockers, 20, 500)
4161
+ } : undefined
4162
+ }
4163
+ });
4164
+ }
4165
+
4166
+ export function buildAICoderAIRunFromEvidence(input: AICoderAIRunAdapterInput): AIRun {
4167
+ const app = input.app || {};
4168
+ const job = input.job || {};
4169
+ const evidence = buildAICoderQaEvidence(input);
4170
+ const events: AIRunEvent[] = [];
4171
+ const gates: AIRunGateResult[] = [];
4172
+ const sourceIds: Record<string, string> = {};
4173
+ addSourceId(sourceIds, 'appId', app._id || app.id || job.appId || job.aicoderAppId);
4174
+ addSourceId(sourceIds, 'jobId', job._id || job.id || job.jobId);
4175
+ addSourceId(sourceIds, 'domain', app.domain || app.customDomain || job.domain);
4176
+
4177
+ for (const log of asArray<Record<string, any>>(input.logs)) {
4178
+ pushEvent(events, {
4179
+ type: log.type === 'scorecard' ? 'scorecard' : 'log',
4180
+ category: cleanText(log.category || log.phase || log.level, 160),
4181
+ message: cleanText(log.message || log.summary || log.text || log.title, 1800),
4182
+ artifactPaths: asArray<string>(log.artifactPaths || log.artifacts),
4183
+ recordedAt: eventDate(log),
4184
+ metadata: {
4185
+ status: log.status,
4186
+ level: log.level,
4187
+ score: log.score
4188
+ }
4189
+ });
4190
+ }
4191
+ collectCommitEvents(asArray<Record<string, any>>(input.commits), events);
4192
+ collectHotfixEvidenceEvents([job, app, evidence, input.qaEvidence], events, gates, input.now);
4193
+ const noBlindLoopGate = aicoderManagerNoBlindLoopGate(app, job, evidence, input.now);
4194
+ if (noBlindLoopGate) {
4195
+ gates.push(noBlindLoopGate);
4196
+ pushEvent(events, {
4197
+ type: 'log',
4198
+ category: 'manager_no_blind_loop_policy',
4199
+ message: noBlindLoopGate.reason,
4200
+ artifactPaths: noBlindLoopGate.evidenceRefs,
4201
+ recordedAt: noBlindLoopGate.recordedAt,
4202
+ metadata: noBlindLoopGate.metadata
4203
+ });
4204
+ }
4205
+ const cost = collectUsageEvents(asArray<Record<string, any>>(input.usageLedger), events);
4206
+ const costGate = costObservabilityGate(cost, 'aicoder_app', input.now);
4207
+ gates.push(costGate);
4208
+ pushEvent(events, {
4209
+ type: 'usage',
4210
+ category: 'cost_observability',
4211
+ message: costGate.reason,
4212
+ artifactPaths: costGate.evidenceRefs,
4213
+ recordedAt: costGate.recordedAt,
4214
+ metadata: costGate.metadata
4215
+ });
4216
+ const journeyContractValidation = aicoderJourneyContractValidationFromEvidence(evidence);
4217
+ const journeyContractGate = aicoderJourneyContractGate(journeyContractValidation, evidence, input.now);
4218
+ gates.push(journeyContractGate);
4219
+ pushEvent(events, {
4220
+ type: 'log',
4221
+ category: 'aicoder_journey_contract',
4222
+ message: journeyContractGate.reason,
4223
+ artifactPaths: journeyContractGate.evidenceRefs,
4224
+ recordedAt: journeyContractGate.recordedAt,
4225
+ metadata: journeyContractGate.metadata
4226
+ });
4227
+ const scorecardPassed = evidence.scorecardPassed === true
4228
+ || evidence.scorecardStatus === 'pass'
4229
+ || (Number(evidence.score ?? evidence.qualityScore ?? 0) >= Number(evidence.scoreThreshold ?? job.scoreThreshold ?? 90));
4230
+ const explicitWorkflowReadiness = normalizeAICoderWorkflowProofReadiness(plainObject(
4231
+ evidence.workflowProofReadiness
4232
+ || evidence.workflow_proof_readiness
4233
+ || evidence.aicoderWorkflowProofReadiness
4234
+ || evidence.aicoder_workflow_proof_readiness
4235
+ || job.workflowProofReadiness
4236
+ || job.workflow_proof_readiness
4237
+ || job.aicoderWorkflowProofReadiness
4238
+ || job.aicoder_workflow_proof_readiness
4239
+ ), input.now);
4240
+ const workflowReadiness = explicitWorkflowReadiness || evaluateResolveIOAICoderWorkflowProofReadiness({
4241
+ journeyContract: evidence.journeyContract,
4242
+ journeyContractMarkdown: evidence.journeyContractMarkdown,
4243
+ workflowQaRows: asArray(evidence.workflowQaRows || evidence.workflow_qa_rows),
4244
+ businessAssertions: [
4245
+ ...asArray(evidence.businessAssertions),
4246
+ ...asArray(evidence.business_assertions),
4247
+ ...asArray(evidence.workflowAssertions),
4248
+ ...asArray(evidence.workflow_assertions)
4249
+ ],
4250
+ routeProbes: [
4251
+ ...asArray(evidence.routeProbes),
4252
+ ...asArray(evidence.route_probes),
4253
+ ...asArray(evidence.workflowProbes),
4254
+ ...asArray(evidence.workflow_probes)
4255
+ ],
4256
+ artifactPaths: [
4257
+ ...asArray(evidence.artifactPaths),
4258
+ ...asArray(evidence.artifact_paths)
4259
+ ],
4260
+ scorecardPassed,
4261
+ deployStatus: evidence.deployStatus,
4262
+ publishStatus: evidence.publishStatus,
4263
+ sampleDataStatus: evidence.sampleDataStatus,
4264
+ previousWorkflowProofFingerprint: firstNonEmptyText([
4265
+ evidence.previousWorkflowProofFingerprint,
4266
+ evidence.previous_workflow_proof_fingerprint,
4267
+ job.previousWorkflowProofFingerprint,
4268
+ job.previous_workflow_proof_fingerprint,
4269
+ app.previousWorkflowProofFingerprint,
4270
+ app.previous_workflow_proof_fingerprint
4271
+ ], 200),
4272
+ previousArtifactFingerprint: firstNonEmptyText([
4273
+ evidence.previousArtifactFingerprint,
4274
+ evidence.previous_artifact_fingerprint,
4275
+ job.previousArtifactFingerprint,
4276
+ job.previous_artifact_fingerprint,
4277
+ app.previousArtifactFingerprint,
4278
+ app.previous_artifact_fingerprint
4279
+ ], 200),
4280
+ previousWorkflowProofFingerprints: [
4281
+ ...asArray(evidence.previousWorkflowProofFingerprints),
4282
+ ...asArray(evidence.previous_workflow_proof_fingerprints),
4283
+ ...asArray(job.previousWorkflowProofFingerprints),
4284
+ ...asArray(job.previous_workflow_proof_fingerprints),
4285
+ ...asArray(app.previousWorkflowProofFingerprints),
4286
+ ...asArray(app.previous_workflow_proof_fingerprints)
4287
+ ],
4288
+ previousArtifactFingerprints: [
4289
+ ...asArray(evidence.previousArtifactFingerprints),
4290
+ ...asArray(evidence.previous_artifact_fingerprints),
4291
+ ...asArray(job.previousArtifactFingerprints),
4292
+ ...asArray(job.previous_artifact_fingerprints),
4293
+ ...asArray(app.previousArtifactFingerprints),
4294
+ ...asArray(app.previous_artifact_fingerprints)
4295
+ ],
4296
+ now: input.now
4297
+ });
4298
+ const workflowReadinessGate = aicoderWorkflowProofReadinessGate(workflowReadiness, input.now);
4299
+ gates.push(workflowReadinessGate);
4300
+ pushEvent(events, {
4301
+ type: 'qa_business_assertion',
4302
+ category: 'aicoder_workflow_proof_readiness',
4303
+ message: workflowReadinessGate.reason,
4304
+ artifactPaths: workflowReadinessGate.evidenceRefs,
4305
+ recordedAt: workflowReadinessGate.recordedAt,
4306
+ metadata: workflowReadinessGate.metadata
4307
+ });
4308
+ const workflowProofCheckpoint = aicoderWorkflowProofCheckpointObject(app, job, evidence);
4309
+ const workflowProofCheckpointGate = aicoderWorkflowProofCheckpointGate(workflowProofCheckpoint, input.now);
4310
+ if (workflowProofCheckpointGate) {
4311
+ gates.push(workflowProofCheckpointGate);
4312
+ pushEvent(events, {
4313
+ type: 'log',
4314
+ category: 'aicoder_workflow_proof_checkpoint',
4315
+ message: workflowProofCheckpointGate.reason,
4316
+ artifactPaths: workflowProofCheckpointGate.evidenceRefs,
4317
+ recordedAt: workflowProofCheckpointGate.recordedAt,
4318
+ metadata: workflowProofCheckpointGate.metadata
4319
+ });
4320
+ }
4321
+ const nextActionContract = aicoderNextActionContractObject(app, job, evidence);
4322
+ const nextActionContractGate = aicoderNextActionContractGate(nextActionContract, input.now);
4323
+ if (nextActionContractGate) {
4324
+ gates.push(nextActionContractGate);
4325
+ pushEvent(events, {
4326
+ type: 'log',
4327
+ category: 'aicoder_next_action_contract',
4328
+ message: nextActionContractGate.reason,
4329
+ artifactPaths: nextActionContractGate.evidenceRefs,
4330
+ recordedAt: nextActionContractGate.recordedAt,
4331
+ metadata: nextActionContractGate.metadata
4332
+ });
4333
+ }
4334
+ const qa = applyAICoderWorkflowProofGate(buildQaFromEvidence(evidence, input.now), workflowReadiness, input.now);
4335
+
4336
+ return buildAIRun({
4337
+ source: 'aicoder_app',
4338
+ sourceIds,
4339
+ title: firstText(app, ['name', 'title']) || firstText(job, ['appName', 'title', 'goal']),
4340
+ status: firstText(job, ['status', 'state']) || firstText(app, ['status', 'state']),
4341
+ phase: firstText(job, ['phase', 'runnerPhase']),
4342
+ startedAt: dateValue(job, ['startedAt', 'createdAt']) || dateValue(app, ['createdAt']),
4343
+ completedAt: dateValue(job, ['completedAt', 'finishedAt', 'updatedAt']),
4344
+ events,
4345
+ gates,
4346
+ qa,
4347
+ cost,
4348
+ scorecardPassed,
4349
+ deployStatus: evidence.deployStatus || statusFromBoolean(evidence.deployReady),
4350
+ publishStatus: evidence.publishStatus || evidence.pullRequestStatus,
4351
+ sampleDataStatus: evidence.sampleDataStatus || statusFromBoolean(evidence.sampleDataReady ?? evidence.hasSampleData),
4352
+ manualHandoff: evidence.manualHandoff === true || job.manualHandoff === true,
4353
+ stopped: evidence.stopped === true || job.stopped === true,
4354
+ rejected: evidence.rejected === true,
4355
+ explicitAccepted: evidence.explicitAccepted === true || app.accepted === true,
4356
+ terminal: /complete|closed|resolved|done|failed|rejected/i.test(`${job.status || ''} ${job.phase || ''}`),
4357
+ now: input.now,
4358
+ metadata: {
4359
+ domain: app.domain || app.customDomain || job.domain,
4360
+ wowScore: evidence.wowScore ?? job.wowScore,
4361
+ score: evidence.score ?? evidence.qualityScore,
4362
+ costObservability: costObservabilityMetadata(costGate),
4363
+ hotfixDurability: hotfixDurabilityMetadata(gates),
4364
+ managerNoBlindLoopPolicy: managerNoBlindLoopMetadata(noBlindLoopGate),
4365
+ journeyContract: {
4366
+ status: journeyContractGate.status,
4367
+ valid: journeyContractGate.metadata?.valid === true,
4368
+ path: cleanText(journeyContractGate.metadata?.path, 500),
4369
+ primaryWorkflowId: cleanText(journeyContractGate.metadata?.primaryWorkflowId, 200),
4370
+ workflowQaRowCount: Number(journeyContractGate.metadata?.workflowQaRowCount || 0),
4371
+ workflowQaCoverageTags: cleanStringList(journeyContractGate.metadata?.workflowQaCoverageTags, 80, 120),
4372
+ workflowQaProofKinds: cleanStringList(journeyContractGate.metadata?.workflowQaProofKinds, 80, 120),
4373
+ errorCount: Number(journeyContractGate.metadata?.errorCount || 0),
4374
+ warningCount: Number(journeyContractGate.metadata?.warningCount || 0),
4375
+ issueCodes: cleanStringList(journeyContractGate.metadata?.issueCodes, 40, 120),
4376
+ blocksBuildUntilValid: journeyContractGate.metadata?.blocksBuildUntilValid === true,
4377
+ blocksWorkflowQaUntilValid: journeyContractGate.metadata?.blocksWorkflowQaUntilValid === true,
4378
+ blocksWowUiUntilWorkflowProof: journeyContractGate.metadata?.blocksWowUiUntilWorkflowProof === true,
4379
+ blocksPublishUntilWorkflowProof: journeyContractGate.metadata?.blocksPublishUntilWorkflowProof === true,
4380
+ nextAction: cleanText(journeyContractGate.metadata?.nextAction, 1000)
4381
+ },
4382
+ workflowProofReadiness: {
4383
+ ready: workflowReadiness.ready,
4384
+ status: workflowReadiness.status,
4385
+ reason: workflowReadiness.reason,
4386
+ blockers: workflowReadiness.blockers,
4387
+ primaryWorkflowId: workflowReadiness.primaryWorkflowId,
4388
+ passedWorkflowRows: workflowReadiness.passedWorkflowRows,
4389
+ missingWorkflowRows: workflowReadiness.missingWorkflowRows,
4390
+ failedWorkflowRows: workflowReadiness.failedWorkflowRows,
4391
+ sampleDataReady: workflowReadiness.sampleDataReady,
4392
+ releaseBlockers: workflowReadiness.releaseBlockers,
4393
+ workflowProofFingerprint: workflowReadiness.workflowProofFingerprint,
4394
+ artifactFingerprint: workflowReadiness.artifactFingerprint,
4395
+ proofFreshness: workflowReadiness.proofFreshness
4396
+ },
4397
+ workflowProofCheckpoint: workflowProofCheckpointGate ? {
4398
+ status: cleanText(workflowProofCheckpointGate.metadata?.status, 120),
4399
+ gateStatus: workflowProofCheckpointGate.status,
4400
+ readinessStatus: cleanText(workflowProofCheckpointGate.metadata?.readinessStatus, 120),
4401
+ nextGate: cleanText(workflowProofCheckpointGate.metadata?.nextGate, 120),
4402
+ nextAction: cleanText(workflowProofCheckpointGate.metadata?.nextAction, 1000),
4403
+ blocksProductRepairUntilJourneyContract: workflowProofCheckpointGate.metadata?.blocksProductRepairUntilJourneyContract === true,
4404
+ blocksPublishUntilWorkflowProof: workflowProofCheckpointGate.metadata?.blocksPublishUntilWorkflowProof === true,
4405
+ blocksPublishUntilReleaseGate: workflowProofCheckpointGate.metadata?.blocksPublishUntilReleaseGate === true,
4406
+ blocksWowUiUntilWorkflowProof: workflowProofCheckpointGate.metadata?.blocksWowUiUntilWorkflowProof === true,
4407
+ workflowProofFingerprint: cleanText(workflowProofCheckpointGate.metadata?.workflowProofFingerprint, 200),
4408
+ artifactFingerprint: cleanText(workflowProofCheckpointGate.metadata?.artifactFingerprint, 200),
4409
+ blockers: cleanStringList(workflowProofCheckpointGate.metadata?.blockers, 20, 500)
4410
+ } : undefined,
4411
+ nextActionContract: nextActionContractGate ? {
4412
+ gateStatus: nextActionContractGate.status,
4413
+ contractId: cleanText(nextActionContractGate.metadata?.contractId, 200),
4414
+ action: cleanText(nextActionContractGate.metadata?.action, 120),
4415
+ label: cleanText(nextActionContractGate.metadata?.label, 200),
4416
+ primaryCommand: cleanText(nextActionContractGate.metadata?.primaryCommand, 200),
4417
+ lane: cleanText(nextActionContractGate.metadata?.lane, 120),
4418
+ stepType: cleanText(nextActionContractGate.metadata?.stepType, 120),
4419
+ safeToAutoRun: nextActionContractGate.metadata?.safeToAutoRun === true,
4420
+ requiresHumanApproval: nextActionContractGate.metadata?.requiresHumanApproval === true,
4421
+ canRunWithoutCodexMonitor: nextActionContractGate.metadata?.canRunWithoutCodexMonitor === true,
4422
+ codexFallbackRequired: nextActionContractGate.metadata?.codexFallbackRequired === true,
4423
+ costRisk: cleanText(nextActionContractGate.metadata?.costRisk, 120),
4424
+ workflowFirstSatisfied: nextActionContractGate.metadata?.workflowFirstSatisfied === true,
4425
+ hotfixCommitRequired: nextActionContractGate.metadata?.hotfixCommitRequired === true,
4426
+ liveHotfixBlockedUntilCommit: nextActionContractGate.metadata?.liveHotfixBlockedUntilCommit === true,
4427
+ decisionBasis: plainObject(nextActionContractGate.metadata?.decisionBasis),
4428
+ preconditions: cleanStringList(nextActionContractGate.metadata?.preconditions, 20, 500),
4429
+ successEvidence: cleanStringList(nextActionContractGate.metadata?.successEvidence, 20, 500),
4430
+ requiredHotfixCommitProof: cleanStringList(nextActionContractGate.metadata?.requiredHotfixCommitProof, 20, 240),
4431
+ stopConditions: cleanStringList(nextActionContractGate.metadata?.stopConditions, 20, 500),
4432
+ forbiddenActions: cleanStringList(nextActionContractGate.metadata?.forbiddenActions, 20, 500),
4433
+ nextCommands: cleanStringList(nextActionContractGate.metadata?.nextCommands, 20, 240),
4434
+ blockers: cleanStringList(nextActionContractGate.metadata?.blockers, 20, 500)
4435
+ } : undefined
4436
+ }
4437
+ });
4438
+ }
4439
+
4440
+ export function buildAssistantAIRunFromEvidence(input: AssistantAIRunAdapterInput): AIRun {
4441
+ const conversation = input.conversation || {};
4442
+ const events: AIRunEvent[] = [];
4443
+ const sourceIds: Record<string, string> = {};
4444
+ addSourceId(sourceIds, 'runId', input.runId);
4445
+ addSourceId(sourceIds, 'requestId', input.requestId);
4446
+ addSourceId(sourceIds, 'messageId', input.messageId);
4447
+ addSourceId(sourceIds, 'conversationId', conversation._id || conversation.id || conversation.conversationId);
4448
+ addSourceId(sourceIds, 'userId', conversation.userId || conversation.ownerId);
4449
+
4450
+ for (const message of asArray<Record<string, any>>(input.messages)) {
4451
+ pushEvent(events, {
4452
+ type: 'assistant_message',
4453
+ category: cleanText(message.role || message.authorRole || message.type, 160),
4454
+ message: cleanText(message.content || message.text || message.message || message.summary, 1800),
4455
+ recordedAt: eventDate(message),
4456
+ metadata: {
4457
+ id: message._id || message.id,
4458
+ role: message.role,
4459
+ model: message.model || message?.usage?.model
4460
+ }
4461
+ });
4462
+ }
4463
+ for (const report of asArray<Record<string, any>>(input.issueReports)) {
4464
+ pushEvent(events, {
4465
+ type: 'human_intervention',
4466
+ category: cleanText(report.category || report.status || 'issue_report', 160),
4467
+ message: cleanText(report.summary || report.description || report.message || report.title, 1800),
4468
+ recordedAt: eventDate(report),
4469
+ metadata: {
4470
+ id: report._id || report.id,
4471
+ status: report.status,
4472
+ severity: report.severity
4473
+ }
4474
+ });
4475
+ }
4476
+ const cost = collectUsageEvents(asArray<Record<string, any>>(input.usageLedger), events);
4477
+ const costGate = costObservabilityGate(cost, 'ai_assistant', input.now);
4478
+ pushEvent(events, {
4479
+ type: 'usage',
4480
+ category: 'cost_observability',
4481
+ message: costGate.reason,
4482
+ artifactPaths: costGate.evidenceRefs,
4483
+ recordedAt: costGate.recordedAt,
4484
+ metadata: costGate.metadata
4485
+ });
4486
+ const answerQuality = assistantQualityObject(input);
4487
+ for (const execution of assistantQueryExecutions(answerQuality).slice(0, 40)) {
4488
+ const status = cleanText(execution.status || execution.queryStatus || execution.query_status || execution.resultStatus || execution.result_status, 120);
4489
+ const toolName = cleanText(execution.tool || execution.toolName || execution.tool_name || execution.type || 'query', 160);
4490
+ const collection = cleanText(execution.collection || execution.collectionName || execution.collection_name, 240);
4491
+ pushEvent(events, {
4492
+ type: 'assistant_query',
4493
+ category: status || toolName,
4494
+ message: cleanText(execution.summary || execution.message || `${toolName}${collection ? ` ${collection}` : ''}`, 1200),
4495
+ artifactPaths: cleanStringList([
4496
+ execution.artifactPath,
4497
+ execution.artifact_path,
4498
+ execution.path,
4499
+ ...asArray(execution.artifactPaths),
4500
+ ...asArray(execution.artifact_paths)
4501
+ ], 20, 500),
4502
+ recordedAt: eventDate(execution),
4503
+ metadata: {
4504
+ id: execution.id || execution._id || execution.ref,
4505
+ tool: toolName,
4506
+ status,
4507
+ collection,
4508
+ resultCount: execution.resultCount ?? execution.result_count ?? execution.count,
4509
+ queryStatus: execution.queryStatus || execution.query_status,
4510
+ legalQueryShape: execution.legalQueryShape ?? execution.legal_query_shape
4511
+ }
4512
+ });
4513
+ }
4514
+ const answerQualityDecision = evaluateAssistantAnswerQuality({
4515
+ answerQuality,
4516
+ correctnessChecks: input.correctnessChecks,
4517
+ now: input.now
4518
+ });
4519
+ const answerQualityGate = assistantAnswerQualityGate(answerQualityDecision, input.now);
4520
+ pushEvent(events, {
4521
+ type: 'assistant_message',
4522
+ category: 'assistant_answer_quality',
4523
+ message: answerQualityGate.reason,
4524
+ artifactPaths: answerQualityGate.evidenceRefs,
4525
+ recordedAt: answerQualityGate.recordedAt,
4526
+ metadata: answerQualityGate.metadata
4527
+ });
4528
+ const answerActionabilityContract = buildAssistantAnswerActionabilityContract(answerQualityDecision, input.now);
4529
+ const answerActionabilityGate = assistantAnswerActionabilityGate(answerActionabilityContract, input.now);
4530
+ pushEvent(events, {
4531
+ type: 'assistant_message',
4532
+ category: 'assistant_answer_actionability',
4533
+ message: answerActionabilityGate.reason,
4534
+ artifactPaths: answerActionabilityGate.evidenceRefs,
4535
+ recordedAt: answerActionabilityGate.recordedAt,
4536
+ metadata: answerActionabilityGate.metadata
4537
+ });
4538
+ const correctnessChecks = asArray<Record<string, any>>(input.correctnessChecks);
4539
+ const hasPassedCorrectnessCheck = correctnessChecks.some((check) => /^(pass|passed|success|ok)$/i.test(cleanText(check.status || (check.passed === true ? 'pass' : ''), 80)));
4540
+ const correctnessEvidence = {
4541
+ businessAssertions: [
4542
+ ...correctnessChecks.map((check) => ({
4543
+ assertion: check.assertion || check.name || check.case || 'assistant correctness check',
4544
+ status: check.status || statusFromBoolean(check.passed),
4545
+ expected: check.expected,
4546
+ observed: check.observed || check.actual,
4547
+ dataProof: check.dataProof || check.proof,
4548
+ artifactPaths: check.artifactPaths,
4549
+ message: check.message || check.reason,
4550
+ metadata: check.metadata,
4551
+ recordedAt: eventDate(check)
4552
+ })),
4553
+ ...(answerQualityDecision.ready && !hasPassedCorrectnessCheck ? [{
4554
+ assertion: 'Assistant answer quality gate passed.',
4555
+ status: 'pass',
4556
+ expected: 'Correct dates, legal query shape, query result classification, cited evidence, confidence, and next action.',
4557
+ observed: answerQualityDecision.reason,
4558
+ dataProof: answerQualityDecision.evidenceRefs.concat(answerQualityDecision.citationRefs).join(', '),
4559
+ artifactPaths: answerQualityDecision.evidenceRefs,
4560
+ message: answerQualityDecision.reason,
4561
+ metadata: {
4562
+ answerQualityStatus: answerQualityDecision.status,
4563
+ queryStatus: answerQualityDecision.queryStatus,
4564
+ dateWindowRequired: answerQualityDecision.dateWindowRequired,
4565
+ dateWindowPresent: answerQualityDecision.dateWindowPresent,
4566
+ dateWindow: answerQualityDecision.dateWindow
4567
+ }
4568
+ }] : [])
4569
+ ]
4570
+ };
4571
+ const qa = applyAssistantAnswerQualityGate(buildQaFromEvidence(correctnessEvidence, input.now), answerQualityDecision, input.now);
4572
+ const failedReports = asArray<Record<string, any>>(input.issueReports)
4573
+ .filter((report) => /open|new|fail|bug|wrong|incorrect|error/i.test(`${report.status || ''} ${report.category || ''} ${report.summary || ''}`));
4574
+
4575
+ return buildAIRun({
4576
+ id: cleanText(input.runId, 240) || undefined,
4577
+ source: 'ai_assistant',
4578
+ sourceIds,
4579
+ title: firstText(conversation, ['title', 'subject', 'summary']) || 'AI assistant conversation',
4580
+ status: firstText(conversation, ['status', 'state']),
4581
+ phase: firstText(conversation, ['phase']),
4582
+ startedAt: dateValue(conversation, ['startedAt', 'createdAt']),
4583
+ completedAt: dateValue(conversation, ['completedAt', 'closedAt', 'updatedAt']),
4584
+ events,
4585
+ gates: [answerActionabilityGate, costGate],
4586
+ qa,
4587
+ cost,
4588
+ rejected: failedReports.length > 0 || conversation.rejected === true,
4589
+ manualHandoff: conversation.manualHandoff === true,
4590
+ stopped: conversation.stopped === true,
4591
+ explicitAccepted: conversation.accepted === true,
4592
+ terminal: /complete|closed|resolved|done|failed|rejected/i.test(`${conversation.status || ''}`),
4593
+ now: input.now,
4594
+ metadata: {
4595
+ issueReportCount: asArray(input.issueReports).length,
4596
+ correctnessCheckCount: correctnessChecks.length,
4597
+ costObservability: costObservabilityMetadata(costGate),
4598
+ answerQuality: {
4599
+ ready: answerQualityDecision.ready,
4600
+ status: answerQualityDecision.status,
4601
+ queryStatus: answerQualityDecision.queryStatus,
4602
+ confidenceLevel: answerQualityDecision.confidenceLevel,
4603
+ dateBasis: answerQualityDecision.dateBasis,
4604
+ expectedCurrentDate: answerQualityDecision.expectedCurrentDate,
4605
+ dateWindowRequired: answerQualityDecision.dateWindowRequired,
4606
+ dateWindowPresent: answerQualityDecision.dateWindowPresent,
4607
+ dateWindow: answerQualityDecision.dateWindow,
4608
+ noDataConfirmed: answerQualityDecision.noDataConfirmed,
4609
+ legalQueryShape: answerQualityDecision.legalQueryShape,
4610
+ citationRefs: answerQualityDecision.citationRefs,
4611
+ queryEvidenceRequired: answerQualityDecision.queryEvidenceRequired,
4612
+ queryEvidencePresent: answerQualityDecision.queryEvidencePresent,
4613
+ queryEvidenceRefs: answerQualityDecision.queryEvidenceRefs,
4614
+ queryExecutionCount: answerQualityDecision.queryExecutionCount,
4615
+ nextActions: answerQualityDecision.nextActions,
4616
+ blockers: answerQualityDecision.blockers
4617
+ },
4618
+ answerActionability: {
4619
+ contractId: answerActionabilityContract.contractId,
4620
+ status: answerActionabilityContract.status,
4621
+ primaryCommand: answerActionabilityContract.primaryCommand,
4622
+ label: answerActionabilityContract.label,
4623
+ canAnswerCustomer: answerActionabilityContract.canAnswerCustomer,
4624
+ canDraftSupportReply: answerActionabilityContract.canDraftSupportReply,
4625
+ canSendCustomerReply: answerActionabilityContract.canSendCustomerReply,
4626
+ requiresHumanReview: answerActionabilityContract.requiresHumanReview,
4627
+ canRunWithoutCodexMonitor: answerActionabilityContract.canRunWithoutCodexMonitor,
4628
+ codexFallbackRequired: answerActionabilityContract.codexFallbackRequired,
4629
+ costRisk: answerActionabilityContract.costRisk,
4630
+ queryResultClass: answerActionabilityContract.queryResultClass,
4631
+ confidenceLevel: answerActionabilityContract.confidenceLevel,
4632
+ decisionBasis: answerActionabilityContract.decisionBasis,
4633
+ requiredEvidence: answerActionabilityContract.requiredEvidence,
4634
+ successEvidence: answerActionabilityContract.successEvidence,
4635
+ blockers: answerActionabilityContract.blockers,
4636
+ nextActions: answerActionabilityContract.nextActions,
4637
+ nextCommands: answerActionabilityContract.nextCommands,
4638
+ forbiddenActions: answerActionabilityContract.forbiddenActions
4639
+ }
4640
+ }
4641
+ });
4642
+ }