@resolveio/server-lib 22.3.175 → 22.3.176

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 +3292 -0
  177. package/src/util/ai-run-evidence-dashboard.ts +313 -0
  178. package/src/util/ai-run-evidence-eval.ts +1054 -0
  179. package/src/util/ai-run-evidence.ts +1124 -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 +4193 -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 +2510 -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 +4785 -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 +1136 -0
  212. package/tests/ai-run-eval.test.ts +112 -0
  213. package/tests/ai-run-evidence.test.ts +1403 -0
  214. package/tests/ai-runner-contract.test.ts +488 -0
  215. package/tests/aicoder-runner-v6.test.ts +676 -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 +1166 -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 -73
  672. package/util/ai-run-evidence-adapters.js +0 -3087
  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 -867
  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 -792
  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 -653
  690. package/util/ai-runner-manager-policy.js +0 -2880
  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 -367
  699. package/util/aicoder-runner-v6.js +0 -1925
  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 -810
  732. package/util/support-runner-v5.js +0 -3497
  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,4785 @@
1
+ import {
2
+ buildResolveIOAIManagerRecoveryCheckpoint,
3
+ buildResolveIOAIManagerRecoveryActionPacket,
4
+ buildResolveIOAIManagerRecoveryEvidenceProbe,
5
+ buildResolveIOAIManagerRecoveryPlan,
6
+ decideResolveIOAIManagerHotfixContinuation,
7
+ decideResolveIOAIManagerPolicy,
8
+ hashResolveIOAIManagerEvidence,
9
+ ResolveIOAIManagerHotfixContinuationDecision,
10
+ ResolveIOAIManagerHotfixFirstReleasePolicy,
11
+ ResolveIOAIManagerEvidenceStrength,
12
+ ResolveIOAIManagerRecoveryCheckpoint,
13
+ ResolveIOAIManagerRecoveryActionPacket,
14
+ ResolveIOAIManagerRecoveryActionDispatchRecord,
15
+ ResolveIOAIManagerRecoveryExecutionDirective,
16
+ ResolveIOAIManagerRecoveryEvidenceProbe,
17
+ ResolveIOAIManagerRecoveryPlan,
18
+ ResolveIOAIManagerRecoveryPlanInput
19
+ } from './ai-runner-manager-policy';
20
+
21
+ export type ResolveIOSupportV5StepType =
22
+ | 'diagnosis_gate'
23
+ | 'reproduction_probe'
24
+ | 'issue_class_probe'
25
+ | 'business_proof'
26
+ | 'compile_check'
27
+ | 'startup_check'
28
+ | 'live_seed'
29
+ | 'auth_bootstrap'
30
+ | 'route_probe'
31
+ | 'qa_row'
32
+ | 'build_repair'
33
+ | 'qa_retest'
34
+ | 'pr_review'
35
+ | 'artifact_package'
36
+ | 'cleanup';
37
+
38
+ export type ResolveIOSupportV5Outcome =
39
+ | 'pass'
40
+ | 'needs_repair'
41
+ | 'retry_same_step'
42
+ | 'park_manual'
43
+ | 'budget_stop'
44
+ | 'infra_retry'
45
+ | 'ready_for_merge';
46
+
47
+ export type ResolveIOSupportV5Lane = 'build' | 'qa' | 'review' | 'supervisor';
48
+
49
+ export type ResolveIOSupportIssueClass =
50
+ | 'no_op_submit'
51
+ | 'missing_wrong_data'
52
+ | 'filter_query_mismatch'
53
+ | 'invoice_pdf_export'
54
+ | 'upload_import'
55
+ | 'route_auth_hydration'
56
+ | 'slow_query_performance';
57
+
58
+ export type ResolveIOSupportDiagnosisGateStatus = 'missing' | 'incomplete' | 'blocked' | 'passed';
59
+
60
+ export type ResolveIOSupportV5FailureClass =
61
+ | 'diagnosis'
62
+ | 'infra'
63
+ | 'compile'
64
+ | 'route'
65
+ | 'business'
66
+ | 'product_code'
67
+ | 'qa_evidence'
68
+ | 'release'
69
+ | 'unknown';
70
+
71
+ export interface ResolveIOSupportDiagnosisIssueCase {
72
+ customer_complaint: string;
73
+ expected_result: string;
74
+ observed_result: string;
75
+ route_module: string;
76
+ account_customer_context: string;
77
+ reproduction_status: 'reproduced' | 'blocked' | 'classified';
78
+ reproduction_blocker?: string;
79
+ }
80
+
81
+ export interface ResolveIOSupportDiagnosisHypothesis {
82
+ statement: string;
83
+ falsifiable_test: string;
84
+ evidence: string[];
85
+ }
86
+
87
+ export interface ResolveIOSupportDiagnosisFailingPath {
88
+ frontend?: string;
89
+ backend?: string;
90
+ shared_library?: string;
91
+ data_query?: string;
92
+ description: string;
93
+ }
94
+
95
+ export interface ResolveIOSupportDiagnosisBusinessProofContract {
96
+ issue_class: ResolveIOSupportIssueClass;
97
+ setup_state: string;
98
+ action_under_test: string;
99
+ expected_business_state_change: string;
100
+ prohibited_false_pass: string;
101
+ proof_artifacts: string[];
102
+ data_or_dom_assertion: string;
103
+ }
104
+
105
+ export interface ResolveIOSupportDiagnosisProofPlan {
106
+ before: string;
107
+ before_state_unavailable_reason?: string;
108
+ action: string;
109
+ after: string;
110
+ business_assertion: string;
111
+ route?: string;
112
+ data_assertion?: string;
113
+ artifact_expectation?: string;
114
+ business_proof_contract?: ResolveIOSupportDiagnosisBusinessProofContract;
115
+ }
116
+
117
+ export interface ResolveIOSupportDiagnosisHint {
118
+ id?: string;
119
+ ticketNumber?: string;
120
+ title?: string;
121
+ outcome?: string;
122
+ issueClass?: string;
123
+ ownerFiles?: string[];
124
+ commitSha?: string;
125
+ commitMessage?: string;
126
+ reason?: string;
127
+ }
128
+
129
+ export type ResolveIOSupportSimilarCaseSource = 'airun' | 'support_ticket' | 'git_commit' | 'manual';
130
+
131
+ export interface ResolveIOSupportSimilarCaseCandidate extends ResolveIOSupportDiagnosisHint {
132
+ source: ResolveIOSupportSimilarCaseSource;
133
+ summary?: string;
134
+ keywords?: string[];
135
+ updatedAt?: string;
136
+ metadata?: Record<string, any>;
137
+ }
138
+
139
+ export interface ResolveIOSupportSimilarCaseRankedHint extends ResolveIOSupportDiagnosisHint {
140
+ source: ResolveIOSupportSimilarCaseSource;
141
+ score: number;
142
+ structuredSignals: string[];
143
+ advisoryOnly: true;
144
+ }
145
+
146
+ export interface ResolveIOSupportSimilarCaseSelectionInput {
147
+ issueClass?: ResolveIOSupportIssueClass | string;
148
+ ownerFiles?: any;
149
+ text?: any;
150
+ candidates?: any[];
151
+ limit?: number;
152
+ now?: Date | string;
153
+ }
154
+
155
+ export interface ResolveIOSupportSimilarCaseSelection {
156
+ ranked: ResolveIOSupportSimilarCaseRankedHint[];
157
+ similarTickets: ResolveIOSupportSimilarCaseRankedHint[];
158
+ similarCommits: ResolveIOSupportSimilarCaseRankedHint[];
159
+ ignoredCount: number;
160
+ generatedAt: string;
161
+ }
162
+
163
+ export type ResolveIOSupportDiagnosisEvidencePackStatus =
164
+ | 'needs_diagnosis'
165
+ | 'diagnosis_ready'
166
+ | 'blocked';
167
+
168
+ export interface ResolveIOSupportIssueClassProbeCatalogEntry {
169
+ issue_class: ResolveIOSupportIssueClass;
170
+ probe_type: ResolveIOSupportV5StepType;
171
+ failure_class: ResolveIOSupportV5FailureClass;
172
+ action: string;
173
+ proof_required: string;
174
+ required_artifacts: string[];
175
+ acceptance_gate: 'aiqa_business_assertion';
176
+ false_pass_blockers: string[];
177
+ }
178
+
179
+ export interface ResolveIOSupportDiagnosisEvidencePack {
180
+ packId: string;
181
+ status: ResolveIOSupportDiagnosisEvidencePackStatus;
182
+ readOnly: true;
183
+ sourceEditsAllowed: false;
184
+ rootCauseFirstRequired: true;
185
+ requiredOutputKey: 'support_diagnosis_gate';
186
+ requiredFields: string[];
187
+ requiredEvidence: string[];
188
+ forbiddenActions: string[];
189
+ diagnosisValid: boolean;
190
+ diagnosisStatus: ResolveIOSupportDiagnosisGateStatus;
191
+ validationBlockers: string[];
192
+ issueClassHint?: ResolveIOSupportIssueClass;
193
+ ownerFileHints: string[];
194
+ activeMicrotaskId?: string;
195
+ similarCaseSelection: ResolveIOSupportSimilarCaseSelection;
196
+ issueClassProbeCatalog: ResolveIOSupportIssueClassProbeCatalogEntry[];
197
+ structuredFacts: Record<string, any>;
198
+ generatedAt: string;
199
+ }
200
+
201
+ export interface ResolveIOSupportDiagnosisEvidencePackInput {
202
+ bundle?: ResolveIOSupportV5StateBundle;
203
+ diagnosisGate?: any;
204
+ similarCaseHints?: any;
205
+ issueClass?: ResolveIOSupportIssueClass | string;
206
+ ownerFiles?: any;
207
+ text?: any;
208
+ ticketContext?: Record<string, any>;
209
+ now?: Date | string;
210
+ }
211
+
212
+ export interface ResolveIOSupportDiagnosisEvidence {
213
+ type: 'ticket' | 'browser' | 'mongo' | 'log' | 'code' | 'commit' | 'qa' | 'other';
214
+ summary: string;
215
+ artifactPath?: string;
216
+ }
217
+
218
+ export interface ResolveIOSupportDiagnosisGate {
219
+ issue_case: ResolveIOSupportDiagnosisIssueCase;
220
+ issue_class: ResolveIOSupportIssueClass;
221
+ accepted_hypothesis: ResolveIOSupportDiagnosisHypothesis;
222
+ rejected_alternatives: string[];
223
+ failing_path: ResolveIOSupportDiagnosisFailingPath;
224
+ owner_files: string[];
225
+ proof_plan: ResolveIOSupportDiagnosisProofPlan;
226
+ similar_tickets?: ResolveIOSupportDiagnosisHint[];
227
+ similar_commits?: ResolveIOSupportDiagnosisHint[];
228
+ evidence: ResolveIOSupportDiagnosisEvidence[];
229
+ status: ResolveIOSupportDiagnosisGateStatus;
230
+ updatedAt?: string;
231
+ }
232
+
233
+ export interface ResolveIOSupportDiagnosisGateValidation {
234
+ valid: boolean;
235
+ status: ResolveIOSupportDiagnosisGateStatus;
236
+ blockers: string[];
237
+ normalized?: ResolveIOSupportDiagnosisGate;
238
+ evidenceQuality?: ResolveIOSupportDiagnosisEvidenceQuality;
239
+ }
240
+
241
+ export interface ResolveIOSupportDiagnosisEvidenceQuality {
242
+ valid: boolean;
243
+ blockers: string[];
244
+ evidenceTypes: ResolveIOSupportDiagnosisEvidence['type'][];
245
+ reproductionEvidenceTypes: ResolveIOSupportDiagnosisEvidence['type'][];
246
+ rootCauseEvidenceTypes: ResolveIOSupportDiagnosisEvidence['type'][];
247
+ artifactBackedEvidenceCount: number;
248
+ hasTicketContext: boolean;
249
+ hasRootCauseEvidence: boolean;
250
+ hasReproductionEvidence: boolean;
251
+ }
252
+
253
+ export type ResolveIOSupportCustomerReplyAction =
254
+ | 'draft_resolution_reply'
255
+ | 'ask_clarification'
256
+ | 'hold_internal';
257
+
258
+ export type ResolveIOSupportHumanReviewType =
259
+ | 'diagnosis_gate'
260
+ | 'owner_scoped_repair'
261
+ | 'business_proof_qa'
262
+ | 'release_hotfix'
263
+ | 'customer_resolution_reply'
264
+ | 'customer_clarification'
265
+ | 'new_evidence'
266
+ | 'internal_hold';
267
+
268
+ export interface ResolveIOSupportHumanReviewPacket {
269
+ reviewType: ResolveIOSupportHumanReviewType;
270
+ title: string;
271
+ summary: string;
272
+ primaryAction: string;
273
+ question?: string;
274
+ customerFacingDraftAllowed: boolean;
275
+ customerSendAllowed: boolean;
276
+ requiresHumanApproval: boolean;
277
+ safety: 'safe_to_draft' | 'needs_clarification' | 'internal_hold';
278
+ reason: string;
279
+ blockers: string[];
280
+ requiredEvidence: string[];
281
+ evidenceRefs: string[];
282
+ nextCommands: string[];
283
+ forbiddenActions: string[];
284
+ costRisk: 'free_or_deterministic' | 'small_model_or_qa' | 'expensive_model' | 'release_or_customer_send';
285
+ createdAt: string;
286
+ }
287
+
288
+ export interface ResolveIOSupportCustomerReplyPolicyInput {
289
+ diagnosisGate?: any;
290
+ outcomeLabel?: string;
291
+ confidence?: any;
292
+ businessAssertionStatus?: string;
293
+ businessAssertions?: any[];
294
+ businessProofArtifacts?: any[];
295
+ previousProofFingerprint?: string;
296
+ previousArtifactFingerprint?: string;
297
+ unresolvedBlockers?: any[];
298
+ releaseStatus?: string;
299
+ }
300
+
301
+ export interface ResolveIOSupportCustomerReplyPolicy {
302
+ action: ResolveIOSupportCustomerReplyAction;
303
+ canDraftCustomerReply: boolean;
304
+ canSendCustomerReply: boolean;
305
+ confidenceLevel: 'high' | 'medium' | 'low' | 'unknown';
306
+ safety: 'safe_to_draft' | 'needs_clarification' | 'internal_hold';
307
+ reason: string;
308
+ requiredEvidence: string[];
309
+ clarificationQuestion?: string;
310
+ humanReviewPacket?: ResolveIOSupportHumanReviewPacket;
311
+ draftBasis?: {
312
+ issueClass: ResolveIOSupportIssueClass;
313
+ businessProof: string;
314
+ artifactCount: number;
315
+ };
316
+ }
317
+
318
+ export interface ResolveIOSupportIssueClassProbe {
319
+ issue_class: ResolveIOSupportIssueClass;
320
+ probe_type: ResolveIOSupportV5StepType;
321
+ failure_class: ResolveIOSupportV5FailureClass;
322
+ objective: string;
323
+ route?: string;
324
+ action: string;
325
+ expected_evidence: string;
326
+ expected_business_proof: string;
327
+ required_artifacts: string[];
328
+ state_transition: {
329
+ before: string;
330
+ action: string;
331
+ after: string;
332
+ assertion: string;
333
+ };
334
+ acceptance_gate: 'aiqa_business_assertion';
335
+ blocks_acceptance_without_business_assertion: boolean;
336
+ }
337
+
338
+ export interface ResolveIOSupportBusinessProofAssertion {
339
+ assertion: string;
340
+ status: string;
341
+ workflow?: string;
342
+ route?: string;
343
+ before?: string;
344
+ action?: string;
345
+ expected?: string;
346
+ after?: string;
347
+ observed?: string;
348
+ dataProof?: string;
349
+ mongoDelta?: Record<string, any>;
350
+ artifactPaths: string[];
351
+ message?: string;
352
+ acceptanceBlocked?: boolean;
353
+ routeOnly?: boolean;
354
+ metadata?: Record<string, any>;
355
+ }
356
+
357
+ export interface ResolveIOSupportBusinessProofReadinessInput {
358
+ diagnosisGate?: any;
359
+ outcomeLabel?: string;
360
+ businessAssertionStatus?: string;
361
+ businessAssertions?: any[];
362
+ businessProofArtifacts?: any[];
363
+ previousProofFingerprint?: string;
364
+ previousArtifactFingerprint?: string;
365
+ }
366
+
367
+ export interface ResolveIOSupportBusinessProofReadiness {
368
+ ready: boolean;
369
+ status: 'missing' | 'route_only' | 'failed' | 'blocked' | 'weak' | 'stale' | 'passed';
370
+ reason: string;
371
+ blockers: string[];
372
+ requiredEvidence: string[];
373
+ artifactPaths: string[];
374
+ proofFingerprint: string;
375
+ artifactFingerprint: string;
376
+ proofFreshness: 'missing' | 'fresh' | 'same_as_previous' | 'stale_artifact' | 'unknown';
377
+ matchedAssertion?: ResolveIOSupportBusinessProofAssertion;
378
+ }
379
+
380
+ export interface ResolveIOSupportV5RepeatStopInput {
381
+ history: ResolveIOSupportV5StepRecord[];
382
+ failureClass?: ResolveIOSupportV5FailureClass | string;
383
+ blocker?: string;
384
+ evidence?: any;
385
+ evidenceHash?: string;
386
+ changedFiles?: any;
387
+ artifactPaths?: any;
388
+ limit?: number;
389
+ ignoreInfra?: boolean;
390
+ }
391
+
392
+ export interface ResolveIOSupportV5RepeatStopDecision {
393
+ shouldStop: boolean;
394
+ repeatedCount: number;
395
+ failureClass: string;
396
+ blockerFingerprint: string;
397
+ evidenceHash: string;
398
+ newEvidence?: boolean;
399
+ materialEvidence?: boolean;
400
+ evidenceStrength?: ResolveIOAIManagerEvidenceStrength;
401
+ evidenceSignals?: string[];
402
+ reason: string;
403
+ }
404
+
405
+ export type ResolveIOSupportEvidenceFreshnessStatus =
406
+ | 'missing'
407
+ | 'fresh'
408
+ | 'retry_below_limit'
409
+ | 'material_evidence'
410
+ | 'weak_evidence'
411
+ | 'stale_repeated'
412
+ | 'ping_pong'
413
+ | 'infra_ignored';
414
+
415
+ export interface ResolveIOSupportEvidenceFreshness {
416
+ status: ResolveIOSupportEvidenceFreshnessStatus;
417
+ failureClass: string;
418
+ blockerFingerprint: string;
419
+ evidenceHash: string;
420
+ sameFailureCount: number;
421
+ pingPongCount: number;
422
+ newEvidence: boolean;
423
+ materialEvidence: boolean;
424
+ evidenceStrength: ResolveIOAIManagerEvidenceStrength;
425
+ evidenceSignals: string[];
426
+ loopBudgetShouldReset: boolean;
427
+ canRetry: boolean;
428
+ mustCollectNewEvidence: boolean;
429
+ productRepairFailure: boolean;
430
+ reason: string;
431
+ requiredResetEvidence: string[];
432
+ changedFiles: string[];
433
+ artifactPaths: string[];
434
+ }
435
+
436
+ export type ResolveIOSupportV5RepairGateAction =
437
+ | 'diagnose_only'
438
+ | 'infra_repair_only'
439
+ | 'allow_product_repair'
440
+ | 'reject_out_of_scope'
441
+ | 'park_repeated_failure';
442
+
443
+ export interface ResolveIOSupportV5RepairGateInput {
444
+ diagnosisGate?: any;
445
+ activeStepType?: ResolveIOSupportV5StepType | string;
446
+ changedFiles?: any;
447
+ artifactPaths?: any;
448
+ failureClass?: ResolveIOSupportV5FailureClass | string;
449
+ blocker?: string;
450
+ evidence?: any;
451
+ evidenceHash?: string;
452
+ history?: ResolveIOSupportV5StepRecord[];
453
+ maxOwnerFiles?: number;
454
+ maxRepeatedNoProgress?: number;
455
+ allowTestsOutsideOwnerFiles?: boolean;
456
+ }
457
+
458
+ export interface ResolveIOSupportV5RepairGateDecision {
459
+ action: ResolveIOSupportV5RepairGateAction;
460
+ canEditProductCode: boolean;
461
+ blockers: string[];
462
+ ownerFiles: string[];
463
+ issueClass?: ResolveIOSupportIssueClass;
464
+ proofPlan?: ResolveIOSupportDiagnosisProofPlan;
465
+ outsideOwnerFiles: string[];
466
+ repeatedFailure?: ResolveIOSupportV5RepeatStopDecision;
467
+ diagnosisValidation: ResolveIOSupportDiagnosisGateValidation;
468
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan;
469
+ recoveryCheckpoint: ResolveIOAIManagerRecoveryCheckpoint;
470
+ recoveryEvidenceProbe: ResolveIOAIManagerRecoveryEvidenceProbe;
471
+ recoveryAction: ResolveIOAIManagerRecoveryActionPacket;
472
+ }
473
+
474
+ export type ResolveIOSupportV5MicrotaskStatus =
475
+ | 'pending'
476
+ | 'in_progress'
477
+ | 'pass'
478
+ | 'needs_repair'
479
+ | 'blocked'
480
+ | 'parked';
481
+
482
+ export type ResolveIOSupportV5MicrotaskType =
483
+ | ResolveIOSupportV5StepType
484
+ | 'planning'
485
+ | 'scope_slice'
486
+ | 'product_repair'
487
+ | 'runner_repair';
488
+
489
+ export interface ResolveIOSupportV5PromptBudget {
490
+ initialPlannerCap: number;
491
+ buildMicrotaskCap: number;
492
+ buildMicrotaskHardCap: number;
493
+ qaMicrotaskCap: number;
494
+ qaMicrotaskHardCap: number;
495
+ repairMicrotaskCap: number;
496
+ repairMicrotaskHardCap: number;
497
+ }
498
+
499
+ export interface ResolveIOSupportV5Microtask {
500
+ microtaskId: string;
501
+ lane: ResolveIOSupportV5Lane;
502
+ type: ResolveIOSupportV5MicrotaskType;
503
+ status: ResolveIOSupportV5MicrotaskStatus;
504
+ objective: string;
505
+ targetFiles: string[];
506
+ contextRefs: string[];
507
+ selfGate: string;
508
+ acceptanceProof: string;
509
+ threadKey: string;
510
+ promptTokenEstimate?: number;
511
+ attempts: number;
512
+ dependsOn: string[];
513
+ parentScopeId?: string;
514
+ blocker?: string;
515
+ createdAt: string;
516
+ updatedAt: string;
517
+ }
518
+
519
+ export interface ResolveIOSupportV5UsageSection {
520
+ name: string;
521
+ tokenEstimate: number;
522
+ }
523
+
524
+ export interface ResolveIOSupportV5MicrotaskUsage {
525
+ microtaskId: string;
526
+ lane: ResolveIOSupportV5Lane;
527
+ model?: string;
528
+ threadKey?: string;
529
+ reuseThread: boolean;
530
+ freshReason?: string;
531
+ promptTokenEstimate: number;
532
+ promptSections: ResolveIOSupportV5UsageSection[];
533
+ actualInputTokens?: number;
534
+ actualCachedInputTokens?: number;
535
+ actualOutputTokens?: number;
536
+ durationMs?: number;
537
+ outcome?: ResolveIOSupportV5Outcome | ResolveIOSupportV5MicrotaskStatus;
538
+ recordedAt: string;
539
+ }
540
+
541
+ export interface ResolveIOSupportV5Budget {
542
+ maxPromptTokensPerNonInitialStep: number;
543
+ maxLoopsPerTicket: number;
544
+ maxRepeatedNoProgress: number;
545
+ maxRuntimeMinutesPerLoop: number;
546
+ totalPromptTokenEstimate: number;
547
+ totalRuntimeMs: number;
548
+ loopCount: number;
549
+ }
550
+
551
+ export interface ResolveIOSupportV5LaneMemory {
552
+ lane: ResolveIOSupportV5Lane;
553
+ model: string;
554
+ threadKey: string;
555
+ scopeSummary: string;
556
+ activeBlocker: string;
557
+ activeQaRow?: {
558
+ index?: number;
559
+ workflow?: string;
560
+ route?: string;
561
+ assertion?: string;
562
+ status?: string;
563
+ };
564
+ changedFiles: string[];
565
+ artifactPaths: string[];
566
+ latestPromptTokenEstimate?: number;
567
+ updatedAt: string;
568
+ }
569
+
570
+ export interface ResolveIOSupportV5FailureFingerprint {
571
+ stepType: ResolveIOSupportV5StepType;
572
+ failureClass: string;
573
+ blockerFingerprint: string;
574
+ evidenceHash: string;
575
+ recordedAt: string;
576
+ }
577
+
578
+ export interface ResolveIOSupportV5SupervisorState {
579
+ version: 'v5';
580
+ status: 'active' | 'parked' | 'complete';
581
+ currentGoal: string;
582
+ approvedScope: string;
583
+ prBranch: string;
584
+ activeStep: ResolveIOSupportV5StepType;
585
+ activeBlocker: string;
586
+ lastGoodCheckpoint: string;
587
+ currentQaRow?: ResolveIOSupportV5LaneMemory['activeQaRow'];
588
+ processLease?: {
589
+ runId?: string;
590
+ token?: string;
591
+ generation?: number;
592
+ workspace?: string;
593
+ lane?: string;
594
+ };
595
+ artifactLinks: string[];
596
+ noEmailUnlessApproved: boolean;
597
+ updatedAt: string;
598
+ }
599
+
600
+ export interface ResolveIOSupportV5StepRecord {
601
+ microtaskId?: string;
602
+ stepType: ResolveIOSupportV5StepType;
603
+ outcome: ResolveIOSupportV5Outcome;
604
+ lane: ResolveIOSupportV5Lane;
605
+ model?: string;
606
+ threadKey?: string;
607
+ promptTokenEstimate?: number;
608
+ runtimeMs?: number;
609
+ summary: string;
610
+ blocker?: string;
611
+ changedFiles?: string[];
612
+ artifactPaths?: string[];
613
+ diagnosisGate?: ResolveIOSupportDiagnosisGate;
614
+ failureClass?: ResolveIOSupportV5FailureClass | string;
615
+ blockerFingerprint?: string;
616
+ evidenceHash?: string;
617
+ recordedAt: string;
618
+ }
619
+
620
+ export interface ResolveIOSupportV5RunnerIncident {
621
+ incidentClass: string;
622
+ summary: string;
623
+ stepType?: ResolveIOSupportV5StepType;
624
+ blockerFingerprint?: string;
625
+ artifactPaths?: string[];
626
+ recordedAt: string;
627
+ }
628
+
629
+ export interface ResolveIOSupportV5StateBundle {
630
+ supportWorkflowVersion: 'v5';
631
+ supportV5SupervisorState: ResolveIOSupportV5SupervisorState;
632
+ supportV5DiagnosisGate?: ResolveIOSupportDiagnosisGate;
633
+ supportV5DiagnosisEvidencePack?: ResolveIOSupportDiagnosisEvidencePack;
634
+ supportV5LaneMemory: {
635
+ build: ResolveIOSupportV5LaneMemory;
636
+ qa: ResolveIOSupportV5LaneMemory;
637
+ };
638
+ supportV5StepHistory: ResolveIOSupportV5StepRecord[];
639
+ supportV5Budget: ResolveIOSupportV5Budget;
640
+ supportV5RunnerIncidents: ResolveIOSupportV5RunnerIncident[];
641
+ supportV5MicrotaskLedger: ResolveIOSupportV5Microtask[];
642
+ supportV5ActiveMicrotaskId?: string;
643
+ supportV5ScopeDigest?: string;
644
+ supportV5MicrotaskUsageHistory: ResolveIOSupportV5MicrotaskUsage[];
645
+ supportV5FailureFingerprints?: ResolveIOSupportV5FailureFingerprint[];
646
+ supportV5RecoveryPlan?: ResolveIOAIManagerRecoveryPlan;
647
+ supportV5RecoveryCheckpoint?: ResolveIOAIManagerRecoveryCheckpoint;
648
+ supportV5RecoveryEvidenceProbe?: ResolveIOAIManagerRecoveryEvidenceProbe;
649
+ supportV5RecoveryAction?: ResolveIOAIManagerRecoveryActionPacket;
650
+ supportV5RecoveryDispatchHistory?: ResolveIOAIManagerRecoveryActionDispatchRecord[];
651
+ supportV5RecoveryDirective?: ResolveIOAIManagerRecoveryExecutionDirective;
652
+ }
653
+
654
+ export interface ResolveIOSupportV5InitializeInput {
655
+ jobId: string;
656
+ ticketId?: string;
657
+ ticketNumber?: string;
658
+ title?: string;
659
+ description?: string;
660
+ approvedScopeRequirements?: string[];
661
+ approvedScopeHours?: number | null;
662
+ prBranch?: string;
663
+ buildThreadKey?: string;
664
+ qaThreadKey?: string;
665
+ processLease?: ResolveIOSupportV5SupervisorState['processLease'];
666
+ now?: Date | string;
667
+ existing?: Partial<ResolveIOSupportV5StateBundle>;
668
+ }
669
+
670
+ export interface ResolveIOSupportV5StepInput {
671
+ microtaskId?: string;
672
+ stepType: ResolveIOSupportV5StepType;
673
+ outcome: ResolveIOSupportV5Outcome;
674
+ lane: ResolveIOSupportV5Lane;
675
+ model?: string;
676
+ threadKey?: string;
677
+ promptTokenEstimate?: number;
678
+ runtimeMs?: number;
679
+ summary?: string;
680
+ blocker?: string;
681
+ changedFiles?: string[];
682
+ artifactPaths?: string[];
683
+ diagnosisGate?: ResolveIOSupportDiagnosisGate;
684
+ failureClass?: ResolveIOSupportV5FailureClass | string;
685
+ evidenceHash?: string;
686
+ activeQaRow?: ResolveIOSupportV5LaneMemory['activeQaRow'];
687
+ now?: Date | string;
688
+ }
689
+
690
+ export interface ResolveIOSupportV5ContinuationDecision {
691
+ action: 'continue' | 'park';
692
+ reason: string;
693
+ nextStep: ResolveIOSupportV5StepType;
694
+ repeatedNoProgressCount: number;
695
+ budgetExceeded: boolean;
696
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan;
697
+ recoveryCheckpoint: ResolveIOAIManagerRecoveryCheckpoint;
698
+ recoveryEvidenceProbe: ResolveIOAIManagerRecoveryEvidenceProbe;
699
+ recoveryAction: ResolveIOAIManagerRecoveryActionPacket;
700
+ }
701
+
702
+ export type ResolveIOSupportV5AutonomousNextAction =
703
+ | 'run_diagnosis_gate'
704
+ | 'ask_customer_clarification'
705
+ | 'repair_infra_only'
706
+ | 'revise_diagnosis_scope'
707
+ | 'run_owner_scoped_repair'
708
+ | 'run_business_proof_qa'
709
+ | 'repair_release_hotfix_first'
710
+ | 'collect_new_evidence'
711
+ | 'draft_customer_reply'
712
+ | 'ready_for_release_gate'
713
+ | 'park_manual';
714
+
715
+ export type ResolveIOSupportContinuationProofCheckpointStatus =
716
+ | 'waiting_for_state_transition'
717
+ | 'waiting_for_proof'
718
+ | 'waiting_for_new_evidence'
719
+ | 'ready_to_continue';
720
+
721
+ export interface ResolveIOSupportContinuationProofCheckpoint {
722
+ required: boolean;
723
+ status: ResolveIOSupportContinuationProofCheckpointStatus;
724
+ action: ResolveIOSupportV5AutonomousNextAction;
725
+ reason: string;
726
+ startingFailureClass: string;
727
+ startingBlockerFingerprint: string;
728
+ startingEvidenceHash: string;
729
+ requiredEvidence: string[];
730
+ requiredResetEvidence: string[];
731
+ successRequiresNewEvidence: boolean;
732
+ blocksProductRepairUntilChangedEvidence: boolean;
733
+ nextAction: string;
734
+ }
735
+
736
+ export type ResolveIOSupportRootCauseReadinessStatus =
737
+ | 'diagnosis_required'
738
+ | 'customer_clarification_required'
739
+ | 'infra_repair_only'
740
+ | 'scope_revision_required'
741
+ | 'owner_scoped_repair_ready'
742
+ | 'business_proof_required'
743
+ | 'release_hotfix_required'
744
+ | 'release_gate_ready'
745
+ | 'customer_reply_draft_ready'
746
+ | 'collect_new_evidence'
747
+ | 'parked';
748
+
749
+ export interface ResolveIOSupportRootCauseReadiness {
750
+ status: ResolveIOSupportRootCauseReadinessStatus;
751
+ nextGate: 'diagnosis' | 'infra' | 'scope' | 'repair' | 'business_proof' | 'release' | 'customer_reply' | 'evidence' | 'manual';
752
+ nextCommand: string;
753
+ rootCauseFirstSatisfied: boolean;
754
+ diagnosisValid: boolean;
755
+ ownerFilesReady: boolean;
756
+ proofPlanReady: boolean;
757
+ businessProofReady: boolean;
758
+ infraOnly: boolean;
759
+ sameFailureParked: boolean;
760
+ canEditProductCode: boolean;
761
+ canRunIssueClassProbe: boolean;
762
+ canRunBusinessProofQa: boolean;
763
+ canRelease: boolean;
764
+ canDraftCustomerReply: boolean;
765
+ requiresHumanDecision: boolean;
766
+ reason: string;
767
+ blockers: string[];
768
+ ownerFiles: string[];
769
+ issueClass?: ResolveIOSupportIssueClass;
770
+ expectedProof?: string;
771
+ issueClassProbes?: ResolveIOSupportIssueClassProbe[];
772
+ businessProofStatus?: ResolveIOSupportBusinessProofReadiness['status'];
773
+ proofFingerprint?: string;
774
+ artifactFingerprint?: string;
775
+ proofFreshness?: ResolveIOSupportBusinessProofReadiness['proofFreshness'];
776
+ }
777
+
778
+ export type ResolveIOSupportNextActionCostRisk =
779
+ | 'free_or_deterministic'
780
+ | 'small_model_or_qa'
781
+ | 'expensive_model'
782
+ | 'release_or_customer_send'
783
+ | 'manual_blocked';
784
+
785
+ export interface ResolveIOSupportNextActionContract {
786
+ contractId: string;
787
+ action: ResolveIOSupportV5AutonomousNextAction;
788
+ label: string;
789
+ primaryCommand: string;
790
+ lane: ResolveIOSupportV5Lane | 'release' | 'customer';
791
+ stepType: ResolveIOSupportV5StepType | 'release_gate' | 'customer_reply';
792
+ safeToAutoRun: boolean;
793
+ requiresHumanApproval: boolean;
794
+ canRunWithoutCodexMonitor: boolean;
795
+ codexFallbackRequired: boolean;
796
+ codexFallbackReason: string;
797
+ costRisk: ResolveIOSupportNextActionCostRisk;
798
+ rootCauseFirstSatisfied: boolean;
799
+ decisionBasis: {
800
+ diagnosisValid: boolean;
801
+ ownerFilesReady: boolean;
802
+ proofPlanReady: boolean;
803
+ businessProofReady: boolean;
804
+ evidenceFreshnessStatus: ResolveIOSupportEvidenceFreshnessStatus;
805
+ evidenceStrength: ResolveIOAIManagerEvidenceStrength;
806
+ failureClass: string;
807
+ blockerFingerprint: string;
808
+ evidenceHash: string;
809
+ sameFailureCount: number;
810
+ hotfixCommitRequired: boolean;
811
+ liveHotfixBlockedUntilCommit: boolean;
812
+ };
813
+ preconditions: string[];
814
+ expectedStateTransition: string;
815
+ successEvidence: string[];
816
+ stopConditions: string[];
817
+ forbiddenActions: string[];
818
+ ownerFiles: string[];
819
+ blockers: string[];
820
+ nextCommands: string[];
821
+ createdAt: string;
822
+ }
823
+
824
+ export interface ResolveIOSupportV5AutonomousDecisionInput {
825
+ bundle: ResolveIOSupportV5StateBundle;
826
+ changedFiles?: any;
827
+ failureClass?: ResolveIOSupportV5FailureClass | string;
828
+ blocker?: any;
829
+ evidence?: any;
830
+ evidenceHash?: string;
831
+ outcomeLabel?: string;
832
+ confidence?: any;
833
+ businessAssertionStatus?: string;
834
+ businessAssertions?: any[];
835
+ businessProofArtifacts?: any[];
836
+ artifactPaths?: any;
837
+ previousProofFingerprint?: string;
838
+ previousArtifactFingerprint?: string;
839
+ unresolvedBlockers?: any[];
840
+ releaseStatus?: string;
841
+ hotfixEvidence?: any;
842
+ releasePolicy?: ResolveIOAIManagerHotfixFirstReleasePolicy;
843
+ releaseGatePassed?: boolean;
844
+ maxOwnerFiles?: number;
845
+ now?: Date | string;
846
+ }
847
+
848
+ export interface ResolveIOSupportV5AutonomousDecision {
849
+ action: ResolveIOSupportV5AutonomousNextAction;
850
+ label: string;
851
+ reason: string;
852
+ canRunAutonomously: boolean;
853
+ canEditProductCode: boolean;
854
+ canRunModel: boolean;
855
+ canRunQa: boolean;
856
+ canPrepareHotfixPatch: boolean;
857
+ canHotfixBackend: boolean;
858
+ liveHotfixBlockedUntilCommit: boolean;
859
+ canDraftCustomerReply: boolean;
860
+ canSendCustomerReply: boolean;
861
+ lane: ResolveIOSupportV5Lane | 'release' | 'customer';
862
+ stepType: ResolveIOSupportV5StepType | 'release_gate' | 'customer_reply';
863
+ microtaskId?: string;
864
+ primaryCommand: string;
865
+ nextCommands: string[];
866
+ requiredEvidence: string[];
867
+ forbiddenActions: string[];
868
+ blockers: string[];
869
+ ownerFiles: string[];
870
+ issueClass?: ResolveIOSupportIssueClass;
871
+ expectedProof?: string;
872
+ issueClassProbes: ResolveIOSupportIssueClassProbe[];
873
+ activeMicrotask?: ResolveIOSupportV5Microtask;
874
+ diagnosisValidation: ResolveIOSupportDiagnosisGateValidation;
875
+ repairGate: ResolveIOSupportV5RepairGateDecision;
876
+ continuation: ResolveIOSupportV5ContinuationDecision;
877
+ customerReplyPolicy: ResolveIOSupportCustomerReplyPolicy;
878
+ businessProofReadiness: ResolveIOSupportBusinessProofReadiness;
879
+ evidenceFreshness: ResolveIOSupportEvidenceFreshness;
880
+ rootCauseReadiness: ResolveIOSupportRootCauseReadiness;
881
+ continuationProofCheckpoint: ResolveIOSupportContinuationProofCheckpoint;
882
+ nextActionContract: ResolveIOSupportNextActionContract;
883
+ humanReviewPacket: ResolveIOSupportHumanReviewPacket;
884
+ hotfixContinuation?: ResolveIOAIManagerHotfixContinuationDecision;
885
+ recordedAt: string;
886
+ }
887
+
888
+ function isoNow(value?: Date | string): string {
889
+ if (value instanceof Date) {
890
+ return value.toISOString();
891
+ }
892
+ const parsed = value ? new Date(value) : new Date();
893
+ if (Number.isFinite(parsed.getTime())) {
894
+ return parsed.toISOString();
895
+ }
896
+ return new Date().toISOString();
897
+ }
898
+
899
+ function cleanText(value: any, max = 2000): string {
900
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, max);
901
+ }
902
+
903
+ function cleanList(values: any, limit = 20, max = 500): string[] {
904
+ if (!Array.isArray(values)) {
905
+ return [];
906
+ }
907
+ const result: string[] = [];
908
+ for (const value of values) {
909
+ const normalized = cleanText(value, max);
910
+ if (normalized && !result.includes(normalized)) {
911
+ result.push(normalized);
912
+ }
913
+ if (result.length >= limit) {
914
+ break;
915
+ }
916
+ }
917
+ return result;
918
+ }
919
+
920
+ function cleanObject(value: any): Record<string, any> {
921
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
922
+ }
923
+
924
+ function pickText(source: Record<string, any>, fields: string[], max = 1000): string {
925
+ for (const field of fields) {
926
+ const normalized = cleanText(source?.[field], max);
927
+ if (normalized) {
928
+ return normalized;
929
+ }
930
+ }
931
+ return '';
932
+ }
933
+
934
+ function normalizeIssueClass(value: any): ResolveIOSupportIssueClass | '' {
935
+ const normalized = cleanText(value, 80)
936
+ .toLowerCase()
937
+ .replace(/[\s-]+/g, '_');
938
+ const aliases: Record<string, ResolveIOSupportIssueClass> = {
939
+ no_op: 'no_op_submit',
940
+ no_op_submit: 'no_op_submit',
941
+ submit_noop: 'no_op_submit',
942
+ missing_data: 'missing_wrong_data',
943
+ wrong_data: 'missing_wrong_data',
944
+ missing_wrong_data: 'missing_wrong_data',
945
+ filter_mismatch: 'filter_query_mismatch',
946
+ query_mismatch: 'filter_query_mismatch',
947
+ filter_query_mismatch: 'filter_query_mismatch',
948
+ invoice_pdf_export: 'invoice_pdf_export',
949
+ pdf_export: 'invoice_pdf_export',
950
+ export: 'invoice_pdf_export',
951
+ upload: 'upload_import',
952
+ import: 'upload_import',
953
+ upload_import: 'upload_import',
954
+ route_auth: 'route_auth_hydration',
955
+ auth_hydration: 'route_auth_hydration',
956
+ route_auth_hydration: 'route_auth_hydration',
957
+ slow_query: 'slow_query_performance',
958
+ performance: 'slow_query_performance',
959
+ slow_query_performance: 'slow_query_performance'
960
+ };
961
+ return aliases[normalized] || '';
962
+ }
963
+
964
+ function normalizeReproductionStatus(value: any): ResolveIOSupportDiagnosisIssueCase['reproduction_status'] {
965
+ const normalized = cleanText(value, 80).toLowerCase();
966
+ if (/blocked|unable|cannot/.test(normalized)) {
967
+ return 'blocked';
968
+ }
969
+ if (/classif|triag|inferred/.test(normalized)) {
970
+ return 'classified';
971
+ }
972
+ return 'reproduced';
973
+ }
974
+
975
+ function normalizeSupportDiagnosisEvidence(values: any): ResolveIOSupportDiagnosisEvidence[] {
976
+ const allowed = new Set(['ticket', 'browser', 'mongo', 'log', 'code', 'commit', 'qa', 'other']);
977
+ if (!Array.isArray(values)) {
978
+ const summary = cleanText(values, 1200);
979
+ return summary ? [{ type: 'other', summary }] : [];
980
+ }
981
+ const stringEvidence: ResolveIOSupportDiagnosisEvidence[] = values
982
+ .filter((entry) => typeof entry === 'string')
983
+ .map((entry) => ({
984
+ type: 'other' as const,
985
+ summary: cleanText(entry, 1200)
986
+ }));
987
+ const objectEvidence: ResolveIOSupportDiagnosisEvidence[] = values
988
+ .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
989
+ .map((entry) => {
990
+ const type = cleanText(entry.type, 80).toLowerCase();
991
+ return {
992
+ type: (allowed.has(type) ? type : 'other') as ResolveIOSupportDiagnosisEvidence['type'],
993
+ summary: cleanText(entry.summary || entry.message || entry.evidence || entry.reason, 1200),
994
+ artifactPath: cleanText(entry.artifactPath || entry.path || entry.file, 500)
995
+ };
996
+ });
997
+ return stringEvidence.concat(objectEvidence)
998
+ .filter((entry) => entry.summary)
999
+ .slice(0, 20);
1000
+ }
1001
+
1002
+ function normalizeSupportDiagnosisHints(values: any): ResolveIOSupportDiagnosisHint[] {
1003
+ return (Array.isArray(values) ? values : [])
1004
+ .map((entry) => {
1005
+ if (typeof entry === 'string') {
1006
+ return { reason: cleanText(entry, 500) };
1007
+ }
1008
+ const value = cleanObject(entry);
1009
+ return {
1010
+ id: cleanText(value.id || value._id, 160),
1011
+ ticketNumber: cleanText(value.ticketNumber || value.ticket_number || value.sourceTicketNumber, 80),
1012
+ title: cleanText(value.title || value.summary, 300),
1013
+ outcome: cleanText(value.outcome || value.status, 80),
1014
+ issueClass: cleanText(value.issueClass || value.issue_class, 80),
1015
+ ownerFiles: cleanList(value.ownerFiles || value.owner_files || value.files, 8, 240),
1016
+ commitSha: cleanText(value.commitSha || value.commit_sha, 80),
1017
+ commitMessage: cleanText(value.commitMessage || value.commit_message, 300),
1018
+ reason: cleanText(value.reason || value.matchReason || value.match_reason, 500)
1019
+ };
1020
+ })
1021
+ .filter((entry) => entry.id || entry.ticketNumber || entry.title || entry.reason || entry.commitSha)
1022
+ .slice(0, 8);
1023
+ }
1024
+
1025
+ function normalizeSupportSimilarCaseSource(value: any): ResolveIOSupportSimilarCaseSource {
1026
+ const normalized = cleanText(value, 80).toLowerCase();
1027
+ if (/commit|git/.test(normalized)) {
1028
+ return 'git_commit';
1029
+ }
1030
+ if (/ticket/.test(normalized)) {
1031
+ return 'support_ticket';
1032
+ }
1033
+ if (/airun|ai_run|run/.test(normalized)) {
1034
+ return 'airun';
1035
+ }
1036
+ return 'manual';
1037
+ }
1038
+
1039
+ function supportOutcomeLooksAccepted(value: any): boolean {
1040
+ return /^(accepted|pass|passed|complete|completed|merged|released)$/i.test(cleanText(value, 80));
1041
+ }
1042
+
1043
+ function supportOutcomeLooksRejected(value: any): boolean {
1044
+ return /\b(reject|rejected|false_pass|failed|failure|blocked|unknown|stopped|manual_handoff)\b/i.test(cleanText(value, 120));
1045
+ }
1046
+
1047
+ function supportOwnerFileDirectory(value: string): string {
1048
+ const normalized = normalizeOwnerFilePath(value);
1049
+ const parts = normalized.split('/').filter(Boolean);
1050
+ return parts.length > 1 ? parts.slice(0, -1).join('/') : normalized;
1051
+ }
1052
+
1053
+ function supportTextTokens(value: any): string[] {
1054
+ const stop = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'into', 'when', 'where', 'what', 'have', 'has', 'had', 'not', 'but', 'are', 'was', 'were', 'issue', 'ticket', 'support']);
1055
+ return Array.from(new Set(cleanText(value, 3000).toLowerCase()
1056
+ .split(/[^a-z0-9_/-]+/g)
1057
+ .map((token) => token.trim())
1058
+ .filter((token) => token.length >= 3 && !stop.has(token))))
1059
+ .slice(0, 80);
1060
+ }
1061
+
1062
+ function normalizeResolveIOSupportSimilarCaseCandidate(value: any): ResolveIOSupportSimilarCaseCandidate | undefined {
1063
+ const source = cleanObject(value);
1064
+ if (!Object.keys(source).length && typeof value !== 'string') {
1065
+ return undefined;
1066
+ }
1067
+ if (typeof value === 'string') {
1068
+ const summary = cleanText(value, 500);
1069
+ return summary ? {
1070
+ source: 'manual',
1071
+ title: summary,
1072
+ summary,
1073
+ keywords: supportTextTokens(summary)
1074
+ } : undefined;
1075
+ }
1076
+ const metadata = cleanObject(source.metadata);
1077
+ const runSourceIds = cleanObject(source.sourceIds || source.source_ids || metadata.sourceIds || metadata.source_ids);
1078
+ const diagnosis = cleanObject(source.diagnosisGate || source.diagnosis_gate || source.supportV5DiagnosisGate || source.support_v5_diagnosis_gate || metadata.diagnosisGate || metadata.diagnosis_gate);
1079
+ const sourceIds = cleanObject(source.sourceIds || source.source_ids || metadata.sourceIds || metadata.source_ids);
1080
+ const sourceText = [
1081
+ source.title,
1082
+ source.summary,
1083
+ source.message,
1084
+ source.description,
1085
+ source.reason,
1086
+ source.commitMessage,
1087
+ source.commit_message,
1088
+ diagnosis.issue_case?.customer_complaint,
1089
+ diagnosis.accepted_hypothesis?.statement
1090
+ ].filter(Boolean).join(' ');
1091
+ const ownerFiles = cleanList(
1092
+ source.ownerFiles
1093
+ || source.owner_files
1094
+ || source.files
1095
+ || diagnosis.owner_files
1096
+ || diagnosis.ownerFiles
1097
+ || metadata.ownerFiles
1098
+ || metadata.owner_files,
1099
+ 12,
1100
+ 300
1101
+ ).map(normalizeOwnerFilePath).filter(Boolean);
1102
+ const issueClass = cleanText(
1103
+ source.issueClass
1104
+ || source.issue_class
1105
+ || diagnosis.issue_class
1106
+ || diagnosis.issueClass
1107
+ || metadata.issueClass
1108
+ || metadata.issue_class,
1109
+ 80
1110
+ );
1111
+ const candidate: ResolveIOSupportSimilarCaseCandidate = {
1112
+ source: normalizeSupportSimilarCaseSource(source.source || source.type || source.runSource),
1113
+ id: cleanText(source.id || source._id || source.runId || source.run_id, 160),
1114
+ ticketNumber: cleanText(source.ticketNumber || source.ticket_number || source.sourceTicketNumber || sourceIds.ticketNumber || runSourceIds.ticketNumber, 80),
1115
+ title: cleanText(source.title || source.name || source.summary, 300),
1116
+ outcome: cleanText(source.outcome || source.status || source.outcomeLabel || source.outcome_label, 80),
1117
+ issueClass: normalizeIssueClass(issueClass) || cleanText(issueClass, 80),
1118
+ ownerFiles,
1119
+ commitSha: cleanText(source.commitSha || source.commit_sha || source.sha || source.sourceCommitSha || source.source_commit_sha, 80),
1120
+ commitMessage: cleanText(source.commitMessage || source.commit_message || source.message, 300),
1121
+ reason: cleanText(source.reason || source.matchReason || source.match_reason, 500),
1122
+ summary: cleanText(source.summary || source.description || source.message || sourceText, 800),
1123
+ keywords: cleanList(source.keywords || source.tags, 20, 80).concat(supportTextTokens(sourceText)).slice(0, 40),
1124
+ updatedAt: isoNow(source.updatedAt || source.updated_at || source.recordedAt || source.recorded_at || source.createdAt || source.created_at),
1125
+ metadata
1126
+ };
1127
+ if (!candidate.id && !candidate.ticketNumber && !candidate.title && !candidate.commitSha && !candidate.summary) {
1128
+ return undefined;
1129
+ }
1130
+ if (candidate.commitSha && candidate.source === 'manual') {
1131
+ candidate.source = 'git_commit';
1132
+ }
1133
+ if (candidate.ticketNumber && candidate.source === 'manual') {
1134
+ candidate.source = 'support_ticket';
1135
+ }
1136
+ return candidate;
1137
+ }
1138
+
1139
+ export function selectResolveIOSupportSimilarCaseHints(input: ResolveIOSupportSimilarCaseSelectionInput = {}): ResolveIOSupportSimilarCaseSelection {
1140
+ const issueClass = normalizeIssueClass(input.issueClass);
1141
+ const ownerFiles = cleanList(input.ownerFiles, 12, 300).map(normalizeOwnerFilePath).filter(Boolean);
1142
+ const ownerFileSet = new Set(ownerFiles);
1143
+ const ownerDirs = new Set(ownerFiles.map(supportOwnerFileDirectory).filter(Boolean));
1144
+ const textTokens = new Set(supportTextTokens(input.text));
1145
+ const limit = Math.max(1, Math.min(12, Number(input.limit || 6)));
1146
+ const normalized = (Array.isArray(input.candidates) ? input.candidates : [])
1147
+ .map(normalizeResolveIOSupportSimilarCaseCandidate)
1148
+ .filter((candidate): candidate is ResolveIOSupportSimilarCaseCandidate => !!candidate);
1149
+ const ranked: ResolveIOSupportSimilarCaseRankedHint[] = [];
1150
+ let ignoredCount = 0;
1151
+ for (const candidate of normalized) {
1152
+ const outcomeKnown = !!candidate.outcome;
1153
+ const accepted = supportOutcomeLooksAccepted(candidate.outcome) || (!!candidate.commitSha && !supportOutcomeLooksRejected(candidate.outcome));
1154
+ if (outcomeKnown && !accepted) {
1155
+ ignoredCount += 1;
1156
+ continue;
1157
+ }
1158
+ const signals: string[] = [];
1159
+ let score = accepted ? 100 : 40;
1160
+ const candidateIssueClass = normalizeIssueClass(candidate.issueClass);
1161
+ if (issueClass && candidateIssueClass && issueClass === candidateIssueClass) {
1162
+ score += 45;
1163
+ signals.push(`issue_class:${issueClass}`);
1164
+ }
1165
+ const exactOwnerOverlap = candidate.ownerFiles.filter((file) => ownerFileSet.has(normalizeOwnerFilePath(file)));
1166
+ if (exactOwnerOverlap.length) {
1167
+ score += 35 + Math.min(20, exactOwnerOverlap.length * 5);
1168
+ signals.push(`owner_file_overlap:${exactOwnerOverlap.slice(0, 3).join(',')}`);
1169
+ }
1170
+ const directoryOverlap = candidate.ownerFiles
1171
+ .map(supportOwnerFileDirectory)
1172
+ .filter((dir) => dir && ownerDirs.has(dir));
1173
+ if (directoryOverlap.length && !exactOwnerOverlap.length) {
1174
+ score += 18;
1175
+ signals.push(`owner_dir_overlap:${Array.from(new Set(directoryOverlap)).slice(0, 2).join(',')}`);
1176
+ }
1177
+ const candidateTokens = new Set([...(candidate.keywords || []), ...supportTextTokens([candidate.title, candidate.summary, candidate.commitMessage].join(' '))]);
1178
+ const tokenOverlap = Array.from(candidateTokens).filter((token) => textTokens.has(token)).slice(0, 6);
1179
+ if (tokenOverlap.length) {
1180
+ score += Math.min(18, tokenOverlap.length * 3);
1181
+ signals.push(`weak_text_overlap:${tokenOverlap.join(',')}`);
1182
+ }
1183
+ if (candidate.commitSha) {
1184
+ score += 8;
1185
+ signals.push('commit_linked');
1186
+ }
1187
+ if (!signals.length && score < 100) {
1188
+ ignoredCount += 1;
1189
+ continue;
1190
+ }
1191
+ ranked.push({
1192
+ id: candidate.id,
1193
+ ticketNumber: candidate.ticketNumber,
1194
+ title: candidate.title,
1195
+ outcome: candidate.outcome || (candidate.commitSha ? 'commit_hint' : ''),
1196
+ issueClass: candidateIssueClass || candidate.issueClass,
1197
+ ownerFiles: candidate.ownerFiles.slice(0, 8),
1198
+ commitSha: candidate.commitSha,
1199
+ commitMessage: candidate.commitMessage,
1200
+ reason: signals.join('; '),
1201
+ source: candidate.source,
1202
+ score,
1203
+ structuredSignals: signals,
1204
+ advisoryOnly: true
1205
+ });
1206
+ }
1207
+ const sorted = ranked
1208
+ .sort((a, b) => b.score - a.score || String(b.ticketNumber || b.commitSha || '').localeCompare(String(a.ticketNumber || a.commitSha || '')))
1209
+ .slice(0, limit);
1210
+ return {
1211
+ ranked: sorted,
1212
+ similarTickets: sorted.filter((hint) => hint.source !== 'git_commit').slice(0, limit),
1213
+ similarCommits: sorted.filter((hint) => hint.source === 'git_commit' || !!hint.commitSha).slice(0, limit),
1214
+ ignoredCount,
1215
+ generatedAt: isoNow(input.now)
1216
+ };
1217
+ }
1218
+
1219
+ const SUPPORT_DIAGNOSIS_GATE_REQUIRED_FIELDS = [
1220
+ 'issue_case',
1221
+ 'issue_class',
1222
+ 'accepted_hypothesis',
1223
+ 'rejected_alternatives',
1224
+ 'failing_path',
1225
+ 'owner_files',
1226
+ 'proof_plan',
1227
+ 'evidence',
1228
+ 'status'
1229
+ ];
1230
+
1231
+ const SUPPORT_DIAGNOSIS_REQUIRED_EVIDENCE = [
1232
+ 'customer complaint, expected result, observed result, route/module, and account/customer context',
1233
+ 'reproduction proof or blocked-reproduction reason',
1234
+ 'one accepted falsifiable root-cause hypothesis with evidence',
1235
+ 'rejected alternatives showing the runner did not guess',
1236
+ 'frontend/backend/shared failing path',
1237
+ 'small exact owner_files set',
1238
+ 'before/action/after business proof plan',
1239
+ 'AIQaBusinessAssertion acceptance requirement'
1240
+ ];
1241
+
1242
+ const SUPPORT_DIAGNOSIS_FORBIDDEN_ACTIONS = [
1243
+ 'Do not edit source, package, generated, fixture, QA artifact, or local data files while building the diagnosis gate.',
1244
+ 'Do not use similar tickets or commits as proof; they are advisory hints only.',
1245
+ 'Do not broaden owner_files from similar hints without fresh evidence from the current ticket.',
1246
+ 'Do not accept route load, screenshot, scorecard, wow score, or model claim as ticket acceptance.',
1247
+ 'Do not run product-code repair until SupportDiagnosisGate validates.'
1248
+ ];
1249
+
1250
+ const SUPPORT_ISSUE_CLASS_PROBE_CATALOG: ResolveIOSupportIssueClassProbeCatalogEntry[] = [{
1251
+ issue_class: 'no_op_submit',
1252
+ probe_type: 'issue_class_probe',
1253
+ failure_class: 'business',
1254
+ action: 'Submit the reported form/action and prove a persisted state change or expected validation result.',
1255
+ proof_required: 'Before/action/after DOM or data proof that the submit is no longer a no-op.',
1256
+ required_artifacts: ['before form state', 'submit action trace', 'post-submit DOM/data proof', 'method or network result'],
1257
+ acceptance_gate: 'aiqa_business_assertion',
1258
+ false_pass_blockers: ['route loaded only', 'button clicked without persisted result', 'model claim without DOM/data proof']
1259
+ }, {
1260
+ issue_class: 'missing_wrong_data',
1261
+ probe_type: 'issue_class_probe',
1262
+ failure_class: 'business',
1263
+ action: 'Load the named record/list and compare visible data to the expected persisted source.',
1264
+ proof_required: 'Visible data and persisted data match the customer expectation.',
1265
+ required_artifacts: ['expected source record', 'visible DOM/data snapshot', 'persisted data comparison'],
1266
+ acceptance_gate: 'aiqa_business_assertion',
1267
+ false_pass_blockers: ['non-empty page only', 'wrong account/customer context', 'sample substitute for named record']
1268
+ }, {
1269
+ issue_class: 'filter_query_mismatch',
1270
+ probe_type: 'issue_class_probe',
1271
+ failure_class: 'business',
1272
+ action: 'Apply the reported filter/query inputs and assert included/excluded rows or counts.',
1273
+ proof_required: 'Returned rows/counts match the expected dataset and exclude the wrong records.',
1274
+ required_artifacts: ['filter input trace', 'included/excluded row proof', 'query or publication result proof'],
1275
+ acceptance_gate: 'aiqa_business_assertion',
1276
+ false_pass_blockers: ['filter control visible only', 'route screenshot without row proof']
1277
+ }, {
1278
+ issue_class: 'invoice_pdf_export',
1279
+ probe_type: 'issue_class_probe',
1280
+ failure_class: 'business',
1281
+ action: 'Generate the invoice/PDF/export and inspect the generated artifact.',
1282
+ proof_required: 'Generated artifact contains the expected customer-visible rows, totals, fields, or file output.',
1283
+ required_artifacts: ['export trigger trace', 'generated file artifact path', 'parsed artifact content proof'],
1284
+ acceptance_gate: 'aiqa_business_assertion',
1285
+ false_pass_blockers: ['download button visible only', 'unparsed file exists only']
1286
+ }, {
1287
+ issue_class: 'upload_import',
1288
+ probe_type: 'issue_class_probe',
1289
+ failure_class: 'business',
1290
+ action: 'Run the upload/import workflow with a representative file and assert parsed plus persisted results.',
1291
+ proof_required: 'Import result message and persisted rows/counts changed as expected.',
1292
+ required_artifacts: ['input file fixture', 'import result message', 'persisted row/count delta'],
1293
+ acceptance_gate: 'aiqa_business_assertion',
1294
+ false_pass_blockers: ['file picker opens only', 'upload toast without persisted delta']
1295
+ }, {
1296
+ issue_class: 'route_auth_hydration',
1297
+ probe_type: 'issue_class_probe',
1298
+ failure_class: 'route',
1299
+ action: 'Open the route as the affected user and prove authenticated hydration reaches the functional screen.',
1300
+ proof_required: 'Hydrated route shows required controls/data for the affected account and no shell-only fallback.',
1301
+ required_artifacts: ['auth context', 'hydrated DOM proof', 'console/network error log'],
1302
+ acceptance_gate: 'aiqa_business_assertion',
1303
+ false_pass_blockers: ['home route proof', 'login shell proof', 'header-only screenshot']
1304
+ }, {
1305
+ issue_class: 'slow_query_performance',
1306
+ probe_type: 'issue_class_probe',
1307
+ failure_class: 'business',
1308
+ action: 'Run the reported query/workflow with timing/log evidence before and after the fix.',
1309
+ proof_required: 'Timing improves or meets target while result equivalence is preserved.',
1310
+ required_artifacts: ['before timing', 'after timing', 'result equivalence proof', 'query/log trace'],
1311
+ acceptance_gate: 'aiqa_business_assertion',
1312
+ false_pass_blockers: ['compile pass only', 'single timing without before/after comparison']
1313
+ }];
1314
+
1315
+ function normalizeSupportSimilarCaseSelection(value: any, input: ResolveIOSupportSimilarCaseSelectionInput): ResolveIOSupportSimilarCaseSelection {
1316
+ const object = cleanObject(value);
1317
+ if (Array.isArray(value)) {
1318
+ return selectResolveIOSupportSimilarCaseHints({
1319
+ ...input,
1320
+ candidates: value
1321
+ });
1322
+ }
1323
+ if (object.ranked || object.similarTickets || object.similar_tickets || object.similarCommits || object.similar_commits) {
1324
+ const normalizeRanked = (entries: any): ResolveIOSupportSimilarCaseRankedHint[] => (Array.isArray(entries) ? entries : [])
1325
+ .map((entry): ResolveIOSupportSimilarCaseRankedHint | undefined => {
1326
+ const candidate = normalizeResolveIOSupportSimilarCaseCandidate(entry);
1327
+ if (!candidate) {
1328
+ return undefined;
1329
+ }
1330
+ const signals = cleanList((entry || {}).structuredSignals || (entry || {}).structured_signals, 10, 160);
1331
+ return {
1332
+ id: candidate.id,
1333
+ ticketNumber: candidate.ticketNumber,
1334
+ title: candidate.title,
1335
+ outcome: candidate.outcome,
1336
+ issueClass: normalizeIssueClass(candidate.issueClass) || candidate.issueClass,
1337
+ ownerFiles: candidate.ownerFiles.slice(0, 8),
1338
+ commitSha: candidate.commitSha,
1339
+ commitMessage: candidate.commitMessage,
1340
+ reason: cleanText((entry || {}).reason || signals.join('; '), 500),
1341
+ source: candidate.source,
1342
+ score: Number((entry || {}).score || 0),
1343
+ structuredSignals: signals,
1344
+ advisoryOnly: true as const
1345
+ };
1346
+ })
1347
+ .filter((entry): entry is ResolveIOSupportSimilarCaseRankedHint => Boolean(entry))
1348
+ .slice(0, Math.max(1, Math.min(12, Number(input.limit || 6))));
1349
+ const rankedHints = normalizeRanked(object.ranked);
1350
+ const ticketHints = normalizeRanked(object.similarTickets || object.similar_tickets);
1351
+ const commitHints = normalizeRanked(object.similarCommits || object.similar_commits);
1352
+ const merged = rankedHints.length
1353
+ ? rankedHints
1354
+ : [...ticketHints, ...commitHints]
1355
+ .sort((a, b) => b.score - a.score)
1356
+ .slice(0, Math.max(1, Math.min(12, Number(input.limit || 6))));
1357
+ return {
1358
+ ranked: merged,
1359
+ similarTickets: ticketHints.length ? ticketHints : merged.filter((hint) => hint.source !== 'git_commit'),
1360
+ similarCommits: commitHints.length ? commitHints : merged.filter((hint) => hint.source === 'git_commit' || !!hint.commitSha),
1361
+ ignoredCount: Number(object.ignoredCount || object.ignored_count || 0),
1362
+ generatedAt: cleanText(object.generatedAt || object.generated_at, 120) || isoNow(input.now)
1363
+ };
1364
+ }
1365
+ return selectResolveIOSupportSimilarCaseHints(input);
1366
+ }
1367
+
1368
+ export function buildResolveIOSupportDiagnosisEvidencePack(
1369
+ input: ResolveIOSupportDiagnosisEvidencePackInput = {}
1370
+ ): ResolveIOSupportDiagnosisEvidencePack {
1371
+ const bundle = input.bundle;
1372
+ const activeMicrotask = bundle
1373
+ ? selectResolveIOSupportV5ActiveMicrotask(bundle.supportV5MicrotaskLedger || [], bundle.supportV5ActiveMicrotaskId)
1374
+ : undefined;
1375
+ const diagnosisSource = input.diagnosisGate || bundle?.supportV5DiagnosisGate;
1376
+ const validation = validateResolveIOSupportDiagnosisGate(diagnosisSource);
1377
+ const normalizedDiagnosis = validation.normalized;
1378
+ const ownerFileHints = cleanList([
1379
+ ...cleanList(input.ownerFiles, 16, 500),
1380
+ ...cleanList(normalizedDiagnosis?.owner_files, 16, 500),
1381
+ ...cleanList(activeMicrotask?.targetFiles, 8, 500)
1382
+ ], 16, 500).map(normalizeOwnerFilePath).filter(Boolean);
1383
+ const issueClassHint = normalizeIssueClass(input.issueClass || normalizedDiagnosis?.issue_class);
1384
+ const text = cleanText([
1385
+ input.text,
1386
+ bundle?.supportV5ScopeDigest,
1387
+ bundle?.supportV5SupervisorState?.currentGoal,
1388
+ input.ticketContext?.title,
1389
+ input.ticketContext?.subject,
1390
+ input.ticketContext?.summary
1391
+ ].filter(Boolean).join(' '), 3000);
1392
+ const similarInput: ResolveIOSupportSimilarCaseSelectionInput = {
1393
+ issueClass: issueClassHint || normalizedDiagnosis?.issue_class,
1394
+ ownerFiles: ownerFileHints,
1395
+ text,
1396
+ candidates: Array.isArray(input.similarCaseHints) ? input.similarCaseHints : [],
1397
+ limit: 6,
1398
+ now: input.now
1399
+ };
1400
+ const similarCaseSelection = normalizeSupportSimilarCaseSelection(input.similarCaseHints, similarInput);
1401
+ const status: ResolveIOSupportDiagnosisEvidencePackStatus = validation.valid
1402
+ ? 'diagnosis_ready'
1403
+ : validation.status === 'blocked'
1404
+ ? 'blocked'
1405
+ : 'needs_diagnosis';
1406
+ const generatedAt = isoNow(input.now);
1407
+ return {
1408
+ packId: `support-diagnosis-pack-${hashResolveIOSupportV5Evidence({
1409
+ status,
1410
+ issueClassHint,
1411
+ ownerFileHints,
1412
+ blockers: validation.blockers,
1413
+ text,
1414
+ activeMicrotaskId: activeMicrotask?.microtaskId
1415
+ })}`,
1416
+ status,
1417
+ readOnly: true,
1418
+ sourceEditsAllowed: false,
1419
+ rootCauseFirstRequired: true,
1420
+ requiredOutputKey: 'support_diagnosis_gate',
1421
+ requiredFields: SUPPORT_DIAGNOSIS_GATE_REQUIRED_FIELDS,
1422
+ requiredEvidence: SUPPORT_DIAGNOSIS_REQUIRED_EVIDENCE,
1423
+ forbiddenActions: SUPPORT_DIAGNOSIS_FORBIDDEN_ACTIONS,
1424
+ diagnosisValid: validation.valid,
1425
+ diagnosisStatus: validation.status,
1426
+ validationBlockers: validation.blockers,
1427
+ issueClassHint: issueClassHint || undefined,
1428
+ ownerFileHints: ownerFileHints.slice(0, 12),
1429
+ activeMicrotaskId: activeMicrotask?.microtaskId,
1430
+ similarCaseSelection,
1431
+ issueClassProbeCatalog: SUPPORT_ISSUE_CLASS_PROBE_CATALOG,
1432
+ structuredFacts: {
1433
+ currentDiagnosisStatus: validation.status,
1434
+ currentDiagnosisValid: validation.valid,
1435
+ reproductionStatus: normalizedDiagnosis?.issue_case.reproduction_status,
1436
+ evidenceTypes: validation.evidenceQuality?.evidenceTypes || [],
1437
+ reproductionEvidenceTypes: validation.evidenceQuality?.reproductionEvidenceTypes || [],
1438
+ rootCauseEvidenceTypes: validation.evidenceQuality?.rootCauseEvidenceTypes || [],
1439
+ artifactBackedEvidenceCount: validation.evidenceQuality?.artifactBackedEvidenceCount || 0,
1440
+ similarHintCount: similarCaseSelection.ranked.length,
1441
+ similarHintsAdvisoryOnly: true,
1442
+ productRepairBlockedUntilDiagnosisPassed: validation.valid !== true
1443
+ },
1444
+ generatedAt
1445
+ };
1446
+ }
1447
+
1448
+ function normalizeSupportDiagnosisBusinessProofContract(value: any, issueClassHint: any): ResolveIOSupportDiagnosisBusinessProofContract | undefined {
1449
+ const source = cleanObject(value);
1450
+ if (!Object.keys(source).length) {
1451
+ return undefined;
1452
+ }
1453
+ const issueClass = normalizeIssueClass(source.issue_class || source.issueClass || issueClassHint)
1454
+ || normalizeIssueClass(issueClassHint)
1455
+ || 'missing_wrong_data';
1456
+ return {
1457
+ issue_class: issueClass,
1458
+ setup_state: pickText(source, ['setup_state', 'setupState', 'before', 'precondition'], 1000),
1459
+ action_under_test: pickText(source, ['action_under_test', 'actionUnderTest', 'action', 'steps'], 1000),
1460
+ expected_business_state_change: pickText(source, ['expected_business_state_change', 'expectedBusinessStateChange', 'expected_change', 'expectedChange', 'after'], 1000),
1461
+ prohibited_false_pass: pickText(source, ['prohibited_false_pass', 'prohibitedFalsePass', 'false_pass', 'falsePass'], 1000),
1462
+ proof_artifacts: cleanList(source.proof_artifacts || source.proofArtifacts || source.artifacts || source.artifact_paths || source.artifactPaths, 10, 500),
1463
+ data_or_dom_assertion: pickText(source, ['data_or_dom_assertion', 'dataOrDomAssertion', 'data_assertion', 'dataAssertion', 'dom_assertion', 'domAssertion', 'assertion'], 1000)
1464
+ };
1465
+ }
1466
+
1467
+ function normalizeOwnerFilePath(value: any): string {
1468
+ return cleanText(value, 500)
1469
+ .replace(/\\/g, '/')
1470
+ .replace(/^\.\/+/, '')
1471
+ .replace(/^\/+/, '')
1472
+ .replace(/\s+$/g, '');
1473
+ }
1474
+
1475
+ function ownerFileLooksBroad(value: string): boolean {
1476
+ const normalized = normalizeOwnerFilePath(value);
1477
+ return !normalized
1478
+ || normalized.includes('*')
1479
+ || normalized.endsWith('/')
1480
+ || !/\.[a-z0-9]+$/i.test(normalized)
1481
+ || /^(\.|src|server|angular|client|app|lib|packages?)$/i.test(normalized)
1482
+ || /(^|\/)(node_modules|dist|build|coverage|\.git)(\/|$)/i.test(normalized);
1483
+ }
1484
+
1485
+ function proofPlanLooksRouteOnly(proofPlan: ResolveIOSupportDiagnosisProofPlan): boolean {
1486
+ const assertionText = cleanText([
1487
+ proofPlan.business_assertion,
1488
+ proofPlan.action,
1489
+ proofPlan.after,
1490
+ proofPlan.data_assertion,
1491
+ proofPlan.artifact_expectation
1492
+ ].filter(Boolean).join(' '), 3000).toLowerCase();
1493
+ if (!assertionText) {
1494
+ return false;
1495
+ }
1496
+ const routeOnlySignal = /\b(route|page|screen|dashboard|url|component|shell)\b/.test(assertionText)
1497
+ && /\b(loads?|loaded|renders?|rendered|opens?|opened|visible|visibility|hydrates?|hydrated|screenshot)\b/.test(assertionText);
1498
+ const businessSignal = /\b(save|saved|persist|persisted|create|created|update|updated|delete|deleted|filter|filtered|exclude|excluded|include|included|row|rows|record|records|count|counts|total|totals|value|values|data|mongo|query|result|results|invoice|pdf|export|download|upload|import|dropdown|selection|selected|form|submit|submitted|validation|error message|customer|account|user|permission|auth|control|button|status|calculation|performance|duration|latency)\b/.test(assertionText);
1499
+ return routeOnlySignal && !businessSignal;
1500
+ }
1501
+
1502
+ const SUPPORT_REPRODUCTION_EVIDENCE_TYPES = new Set<ResolveIOSupportDiagnosisEvidence['type']>(['browser', 'mongo', 'log', 'qa']);
1503
+ const SUPPORT_ROOT_CAUSE_EVIDENCE_TYPES = new Set<ResolveIOSupportDiagnosisEvidence['type']>(['browser', 'mongo', 'log', 'code', 'commit', 'qa']);
1504
+ const SUPPORT_ARTIFACT_RECOMMENDED_EVIDENCE_TYPES = new Set<ResolveIOSupportDiagnosisEvidence['type']>(['browser', 'mongo', 'log', 'qa']);
1505
+
1506
+ export function evaluateResolveIOSupportDiagnosisEvidenceQuality(value: any): ResolveIOSupportDiagnosisEvidenceQuality {
1507
+ const normalized = normalizeResolveIOSupportDiagnosisGate(value);
1508
+ if (!normalized) {
1509
+ return {
1510
+ valid: false,
1511
+ blockers: ['SupportDiagnosisGate is missing.'],
1512
+ evidenceTypes: [],
1513
+ reproductionEvidenceTypes: [],
1514
+ rootCauseEvidenceTypes: [],
1515
+ artifactBackedEvidenceCount: 0,
1516
+ hasTicketContext: false,
1517
+ hasRootCauseEvidence: false,
1518
+ hasReproductionEvidence: false
1519
+ };
1520
+ }
1521
+ const evidence = normalized.evidence || [];
1522
+ const evidenceTypes = Array.from(new Set(evidence.map((entry) => entry.type)));
1523
+ const reproductionEvidenceTypes = Array.from(new Set(evidence
1524
+ .filter((entry) => SUPPORT_REPRODUCTION_EVIDENCE_TYPES.has(entry.type))
1525
+ .map((entry) => entry.type)));
1526
+ const rootCauseEvidenceTypes = Array.from(new Set(evidence
1527
+ .filter((entry) => SUPPORT_ROOT_CAUSE_EVIDENCE_TYPES.has(entry.type))
1528
+ .map((entry) => entry.type)));
1529
+ const artifactBackedEvidenceCount = evidence.filter((entry) => !!entry.artifactPath).length;
1530
+ const hasTicketContext = evidence.some((entry) => entry.type === 'ticket')
1531
+ || Boolean(normalized.issue_case.customer_complaint && normalized.issue_case.account_customer_context);
1532
+ const hasRootCauseEvidence = rootCauseEvidenceTypes.length > 0;
1533
+ const hasReproductionEvidence = reproductionEvidenceTypes.length > 0;
1534
+ const blockers: string[] = [];
1535
+ if (!evidence.length) {
1536
+ blockers.push('Diagnosis evidence must include typed ticket/code/browser/log/Mongo/QA proof.');
1537
+ }
1538
+ if (!hasRootCauseEvidence) {
1539
+ blockers.push('Diagnosis evidence must include at least one non-ticket root-cause proof item: code, commit, browser, Mongo, log, or QA.');
1540
+ }
1541
+ if (!hasTicketContext) {
1542
+ blockers.push('Diagnosis evidence must preserve the customer/ticket context used for reproduction.');
1543
+ }
1544
+ if (normalized.issue_case.reproduction_status === 'reproduced' && !hasReproductionEvidence) {
1545
+ blockers.push('Diagnosis reproduced issue_case requires browser, Mongo, log, or QA reproduction evidence before repair.');
1546
+ }
1547
+ if (normalized.issue_case.reproduction_status === 'blocked'
1548
+ && !normalized.issue_case.reproduction_blocker) {
1549
+ blockers.push('Diagnosis blocked reproduction requires issue_case.reproduction_blocker.');
1550
+ }
1551
+ const unartifactedRuntimeEvidence = evidence.filter((entry) => SUPPORT_ARTIFACT_RECOMMENDED_EVIDENCE_TYPES.has(entry.type) && !entry.artifactPath);
1552
+ if (unartifactedRuntimeEvidence.length) {
1553
+ blockers.push(`Diagnosis runtime evidence must include artifactPath for replayable proof: ${unartifactedRuntimeEvidence.map((entry) => entry.type).join(', ')}.`);
1554
+ }
1555
+ if (normalized.issue_case.reproduction_status === 'reproduced' && artifactBackedEvidenceCount < 1) {
1556
+ blockers.push('Diagnosis reproduced issue_case requires at least one artifact-backed evidence item.');
1557
+ }
1558
+ return {
1559
+ valid: blockers.length === 0,
1560
+ blockers,
1561
+ evidenceTypes: evidenceTypes as ResolveIOSupportDiagnosisEvidence['type'][],
1562
+ reproductionEvidenceTypes: reproductionEvidenceTypes as ResolveIOSupportDiagnosisEvidence['type'][],
1563
+ rootCauseEvidenceTypes: rootCauseEvidenceTypes as ResolveIOSupportDiagnosisEvidence['type'][],
1564
+ artifactBackedEvidenceCount,
1565
+ hasTicketContext,
1566
+ hasRootCauseEvidence,
1567
+ hasReproductionEvidence
1568
+ };
1569
+ }
1570
+
1571
+ export function normalizeResolveIOSupportDiagnosisGate(value: any, now?: Date | string): ResolveIOSupportDiagnosisGate | undefined {
1572
+ const source = cleanObject(value);
1573
+ if (!Object.keys(source).length) {
1574
+ return undefined;
1575
+ }
1576
+ const issueCaseSource = cleanObject(source.issue_case || source.issueCase);
1577
+ const hypothesisSource = cleanObject(source.accepted_hypothesis || source.acceptedHypothesis);
1578
+ const failingPathSource = cleanObject(source.failing_path || source.failingPath);
1579
+ const proofPlanSource = cleanObject(source.proof_plan || source.proofPlan);
1580
+ const issueClass = normalizeIssueClass(source.issue_class || source.issueClass);
1581
+ const businessProofContract = normalizeSupportDiagnosisBusinessProofContract(
1582
+ proofPlanSource.business_proof_contract || proofPlanSource.businessProofContract || source.business_proof_contract || source.businessProofContract,
1583
+ issueClass || source.issue_class || source.issueClass
1584
+ );
1585
+ const ownerFiles = cleanList(source.owner_files || source.ownerFiles, 20, 500)
1586
+ .map(normalizeOwnerFilePath)
1587
+ .filter(Boolean);
1588
+ const gate: ResolveIOSupportDiagnosisGate = {
1589
+ issue_case: {
1590
+ customer_complaint: pickText(issueCaseSource, ['customer_complaint', 'customerComplaint', 'complaint', 'request', 'summary'], 1200),
1591
+ expected_result: pickText(issueCaseSource, ['expected_result', 'expectedResult', 'expected'], 1000),
1592
+ observed_result: pickText(issueCaseSource, ['observed_result', 'observedResult', 'observed', 'actual'], 1000),
1593
+ route_module: pickText(issueCaseSource, ['route_module', 'routeModule', 'route', 'module', 'screen'], 500),
1594
+ account_customer_context: pickText(issueCaseSource, ['account_customer_context', 'accountCustomerContext', 'account', 'customer', 'user', 'context'], 800),
1595
+ reproduction_status: normalizeReproductionStatus(issueCaseSource.reproduction_status || issueCaseSource.reproductionStatus || source.reproduction_status),
1596
+ reproduction_blocker: pickText(issueCaseSource, ['reproduction_blocker', 'reproductionBlocker', 'blocked_reason', 'blockedReason'], 1000)
1597
+ },
1598
+ issue_class: issueClass || 'missing_wrong_data',
1599
+ accepted_hypothesis: {
1600
+ statement: pickText(hypothesisSource, ['statement', 'hypothesis', 'root_cause', 'rootCause', 'summary'], 1200)
1601
+ || (typeof source.accepted_hypothesis === 'string' ? cleanText(source.accepted_hypothesis, 1200) : ''),
1602
+ falsifiable_test: pickText(hypothesisSource, ['falsifiable_test', 'falsifiableTest', 'test', 'proof', 'falsifier'], 1000),
1603
+ evidence: cleanList(hypothesisSource.evidence || source.hypothesis_evidence || source.hypothesisEvidence, 10, 800)
1604
+ },
1605
+ rejected_alternatives: cleanList(source.rejected_alternatives || source.rejectedAlternatives, 10, 700),
1606
+ failing_path: {
1607
+ frontend: pickText(failingPathSource, ['frontend', 'frontend_path', 'frontendPath', 'eventPath'], 700),
1608
+ backend: pickText(failingPathSource, ['backend', 'backend_path', 'backendPath', 'methodPublicationPath'], 700),
1609
+ shared_library: pickText(failingPathSource, ['shared_library', 'sharedLibrary', 'library', 'lib'], 700),
1610
+ data_query: pickText(failingPathSource, ['data_query', 'dataQuery', 'query'], 700),
1611
+ description: pickText(failingPathSource, ['description', 'path', 'summary'], 1200) || cleanText(source.failing_path, 1200)
1612
+ },
1613
+ owner_files: ownerFiles,
1614
+ proof_plan: {
1615
+ before: pickText(proofPlanSource, ['before', 'before_state', 'beforeState', 'precondition'], 1000),
1616
+ before_state_unavailable_reason: pickText(proofPlanSource, ['before_state_unavailable_reason', 'beforeStateUnavailableReason', 'before_unavailable_reason'], 1000),
1617
+ action: pickText(proofPlanSource, ['action', 'browser_action', 'browserAction', 'steps'], 1000),
1618
+ after: pickText(proofPlanSource, ['after', 'after_state', 'afterState', 'expected_after'], 1000),
1619
+ business_assertion: pickText(proofPlanSource, ['business_assertion', 'businessAssertion', 'assertion'], 1000),
1620
+ route: pickText(proofPlanSource, ['route', 'url'], 500),
1621
+ data_assertion: pickText(proofPlanSource, ['data_assertion', 'dataAssertion', 'mongo_delta', 'mongoDelta'], 1000),
1622
+ artifact_expectation: pickText(proofPlanSource, ['artifact_expectation', 'artifactExpectation', 'artifact', 'screenshot'], 1000),
1623
+ business_proof_contract: businessProofContract
1624
+ },
1625
+ similar_tickets: normalizeSupportDiagnosisHints(source.similar_tickets || source.similarTickets),
1626
+ similar_commits: normalizeSupportDiagnosisHints(source.similar_commits || source.similarCommits),
1627
+ evidence: normalizeSupportDiagnosisEvidence(source.evidence),
1628
+ status: (cleanText(source.status, 80).toLowerCase() as ResolveIOSupportDiagnosisGateStatus) || 'incomplete',
1629
+ updatedAt: isoNow(now || source.updatedAt || source.updated_at)
1630
+ };
1631
+ if (!['missing', 'incomplete', 'blocked', 'passed'].includes(gate.status)) {
1632
+ gate.status = 'incomplete';
1633
+ }
1634
+ return gate;
1635
+ }
1636
+
1637
+ export function extractResolveIOSupportDiagnosisGateFromText(value: string, now?: Date | string): ResolveIOSupportDiagnosisGate | undefined {
1638
+ const text = String(value || '').trim();
1639
+ if (!text) {
1640
+ return undefined;
1641
+ }
1642
+ const candidates: string[] = [];
1643
+ const fenced = Array.from(text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)).map((match) => match[1]);
1644
+ candidates.push(...fenced);
1645
+ const jsonBlock = text.match(/\{[\s\S]*\}/);
1646
+ if (jsonBlock) {
1647
+ candidates.push(jsonBlock[0]);
1648
+ }
1649
+ for (const candidate of candidates) {
1650
+ try {
1651
+ const parsed = JSON.parse(candidate);
1652
+ const wrapped = parsed?.support_diagnosis_gate || parsed?.supportDiagnosisGate || parsed?.diagnosis_gate || parsed?.diagnosisGate || parsed;
1653
+ const normalized = normalizeResolveIOSupportDiagnosisGate(wrapped, now);
1654
+ if (normalized) {
1655
+ return normalized;
1656
+ }
1657
+ }
1658
+ catch {
1659
+ // Continue trying less exact JSON candidates.
1660
+ }
1661
+ }
1662
+ return undefined;
1663
+ }
1664
+
1665
+ export function validateResolveIOSupportDiagnosisGate(
1666
+ value: any,
1667
+ options: { maxOwnerFiles?: number } = {}
1668
+ ): ResolveIOSupportDiagnosisGateValidation {
1669
+ const normalized = normalizeResolveIOSupportDiagnosisGate(value);
1670
+ if (!normalized) {
1671
+ return { valid: false, status: 'missing', blockers: ['SupportDiagnosisGate is missing.'] };
1672
+ }
1673
+ const blockers: string[] = [];
1674
+ const maxOwnerFiles = Math.max(1, Number(options.maxOwnerFiles || 8) || 8);
1675
+ if (!normalized.issue_case.customer_complaint) {
1676
+ blockers.push('Diagnosis issue_case.customer_complaint is required.');
1677
+ }
1678
+ if (!normalized.issue_case.expected_result) {
1679
+ blockers.push('Diagnosis issue_case.expected_result is required.');
1680
+ }
1681
+ if (!normalized.issue_case.observed_result) {
1682
+ blockers.push('Diagnosis issue_case.observed_result is required.');
1683
+ }
1684
+ if (!normalized.issue_case.route_module) {
1685
+ blockers.push('Diagnosis issue_case.route_module is required.');
1686
+ }
1687
+ if (!normalized.issue_case.account_customer_context) {
1688
+ blockers.push('Diagnosis issue_case.account_customer_context is required.');
1689
+ }
1690
+ if (normalized.issue_case.reproduction_status === 'blocked' && !normalized.issue_case.reproduction_blocker) {
1691
+ blockers.push('Diagnosis blocked reproduction requires issue_case.reproduction_blocker.');
1692
+ }
1693
+ if (!normalizeIssueClass(normalized.issue_class)) {
1694
+ blockers.push('Diagnosis issue_class must be one of the supported issue-class probes.');
1695
+ }
1696
+ if (!normalized.accepted_hypothesis.statement) {
1697
+ blockers.push('Diagnosis accepted_hypothesis.statement is required.');
1698
+ }
1699
+ if (!normalized.accepted_hypothesis.falsifiable_test) {
1700
+ blockers.push('Diagnosis accepted_hypothesis.falsifiable_test is required.');
1701
+ }
1702
+ if (!normalized.accepted_hypothesis.evidence.length) {
1703
+ blockers.push('Diagnosis accepted_hypothesis.evidence must cite proof.');
1704
+ }
1705
+ if (!normalized.rejected_alternatives.length) {
1706
+ blockers.push('Diagnosis rejected_alternatives must include at least one rejected theory.');
1707
+ }
1708
+ if (!normalized.failing_path.description
1709
+ && !normalized.failing_path.frontend
1710
+ && !normalized.failing_path.backend
1711
+ && !normalized.failing_path.shared_library
1712
+ && !normalized.failing_path.data_query) {
1713
+ blockers.push('Diagnosis failing_path must identify frontend, backend, query, shared library, or path description.');
1714
+ }
1715
+ if (!normalized.owner_files.length) {
1716
+ blockers.push('Diagnosis owner_files must contain the small editable file set.');
1717
+ }
1718
+ if (normalized.owner_files.length > maxOwnerFiles) {
1719
+ blockers.push(`Diagnosis owner_files has ${normalized.owner_files.length} entries; maximum is ${maxOwnerFiles}.`);
1720
+ }
1721
+ const broadFiles = normalized.owner_files.filter(ownerFileLooksBroad);
1722
+ if (broadFiles.length) {
1723
+ blockers.push(`Diagnosis owner_files contains broad or unsafe path(s): ${broadFiles.join(', ')}.`);
1724
+ }
1725
+ if (!normalized.proof_plan.before && !normalized.proof_plan.before_state_unavailable_reason) {
1726
+ blockers.push('Diagnosis proof_plan.before is required unless proof_plan.before_state_unavailable_reason explains why before-state proof is impossible.');
1727
+ }
1728
+ if (!normalized.proof_plan.action) {
1729
+ blockers.push('Diagnosis proof_plan.action is required.');
1730
+ }
1731
+ if (!normalized.proof_plan.after) {
1732
+ blockers.push('Diagnosis proof_plan.after is required.');
1733
+ }
1734
+ if (!normalized.proof_plan.business_assertion) {
1735
+ blockers.push('Diagnosis proof_plan.business_assertion is required.');
1736
+ }
1737
+ if (proofPlanLooksRouteOnly(normalized.proof_plan)) {
1738
+ blockers.push('Diagnosis proof_plan cannot be route-load, screen-visible, or screenshot-only; it must name the business state/data/control change that proves the issue is fixed.');
1739
+ }
1740
+ const proofContract = normalized.proof_plan.business_proof_contract;
1741
+ if (!proofContract) {
1742
+ blockers.push('Diagnosis proof_plan.business_proof_contract is required.');
1743
+ }
1744
+ else {
1745
+ if (proofContract.issue_class !== normalized.issue_class) {
1746
+ blockers.push(`Diagnosis proof_plan.business_proof_contract.issue_class (${proofContract.issue_class}) must match diagnosis issue_class (${normalized.issue_class}).`);
1747
+ }
1748
+ if (!proofContract.setup_state) {
1749
+ blockers.push('Diagnosis proof_plan.business_proof_contract.setup_state is required.');
1750
+ }
1751
+ if (!proofContract.action_under_test) {
1752
+ blockers.push('Diagnosis proof_plan.business_proof_contract.action_under_test is required.');
1753
+ }
1754
+ if (!proofContract.expected_business_state_change) {
1755
+ blockers.push('Diagnosis proof_plan.business_proof_contract.expected_business_state_change is required.');
1756
+ }
1757
+ if (!proofContract.prohibited_false_pass) {
1758
+ blockers.push('Diagnosis proof_plan.business_proof_contract.prohibited_false_pass is required.');
1759
+ }
1760
+ if (!proofContract.proof_artifacts.length) {
1761
+ blockers.push('Diagnosis proof_plan.business_proof_contract.proof_artifacts must include at least one required artifact.');
1762
+ }
1763
+ if (!proofContract.data_or_dom_assertion) {
1764
+ blockers.push('Diagnosis proof_plan.business_proof_contract.data_or_dom_assertion is required.');
1765
+ }
1766
+ }
1767
+ if (!normalized.evidence.length) {
1768
+ blockers.push('Diagnosis evidence must include ticket/code/browser/log/Mongo proof.');
1769
+ }
1770
+ const evidenceQuality = evaluateResolveIOSupportDiagnosisEvidenceQuality(normalized);
1771
+ blockers.push(...evidenceQuality.blockers);
1772
+ normalized.status = blockers.length ? 'incomplete' : 'passed';
1773
+ return {
1774
+ valid: blockers.length === 0,
1775
+ status: normalized.status,
1776
+ blockers,
1777
+ normalized,
1778
+ evidenceQuality
1779
+ };
1780
+ }
1781
+
1782
+ function normalizeSupportConfidenceLevel(value: any): ResolveIOSupportCustomerReplyPolicy['confidenceLevel'] {
1783
+ const normalized = cleanText(value?.level || value?.confidenceLevel || value?.confidence_level || value?.category || value, 80).toLowerCase();
1784
+ if (/^high$|^strong$|^accepted$|^a\+?$/.test(normalized)) {
1785
+ return 'high';
1786
+ }
1787
+ if (/^medium$|^moderate$|^review$/.test(normalized)) {
1788
+ return 'medium';
1789
+ }
1790
+ if (/^low$|^weak$|^blocked$|^unknown$/.test(normalized)) {
1791
+ return normalized === 'unknown' ? 'unknown' : 'low';
1792
+ }
1793
+ const score = Number(value?.score ?? value?.confidenceScore ?? value?.confidence_score);
1794
+ if (Number.isFinite(score)) {
1795
+ if (score >= 0.8) {
1796
+ return 'high';
1797
+ }
1798
+ if (score >= 0.55) {
1799
+ return 'medium';
1800
+ }
1801
+ return 'low';
1802
+ }
1803
+ return normalized ? 'unknown' : 'unknown';
1804
+ }
1805
+
1806
+ function supportReleaseLooksBlocked(value: any): boolean {
1807
+ return /\b(fail|failed|error|blocked|missing|empty|stale|denied|timeout)\b/i.test(cleanText(value, 200));
1808
+ }
1809
+
1810
+ function supportBusinessAssertionPassed(value: any): boolean {
1811
+ return /^(pass|passed|accepted|business_assertion_passed)$/i.test(cleanText(value, 80));
1812
+ }
1813
+
1814
+ function supportBusinessAssertionFailed(value: any): boolean {
1815
+ return /^(fail|failed|blocked|needs_repair|business_assertion_failed)$/i.test(cleanText(value, 80));
1816
+ }
1817
+
1818
+ function supportBusinessAssertionRouteOnly(value: any): boolean {
1819
+ return /^(route_probe_pass|route_only_pass|route_pass|route_probe|route_loaded)$/i.test(cleanText(value, 80));
1820
+ }
1821
+
1822
+ function normalizeSupportBusinessProofAssertions(input: ResolveIOSupportBusinessProofReadinessInput): ResolveIOSupportBusinessProofAssertion[] {
1823
+ const gate = normalizeResolveIOSupportDiagnosisGate(input.diagnosisGate);
1824
+ const proofPlan = gate?.proof_plan;
1825
+ const proofContract = proofPlan?.business_proof_contract;
1826
+ const artifactPaths = cleanList(input.businessProofArtifacts, 30, 500);
1827
+ const assertions: ResolveIOSupportBusinessProofAssertion[] = [];
1828
+ for (const entry of Array.isArray(input.businessAssertions) ? input.businessAssertions : []) {
1829
+ const source = cleanObject(entry);
1830
+ if (!Object.keys(source).length) {
1831
+ continue;
1832
+ }
1833
+ const metadata = cleanObject(source.metadata);
1834
+ assertions.push({
1835
+ assertion: pickText(source, ['assertion', 'name', 'workflow', 'expected'], 1000) || proofPlan?.business_assertion || 'business assertion',
1836
+ status: cleanText(source.status || source.outcome || source.result, 80),
1837
+ workflow: pickText(source, ['workflow', 'workflowName'], 400),
1838
+ route: pickText(source, ['route', 'url'], 500),
1839
+ before: pickText(source, ['before', 'before_state', 'beforeState'], 1000),
1840
+ action: pickText(source, ['action', 'action_under_test', 'actionUnderTest'], 1000),
1841
+ expected: pickText(source, ['expected', 'expected_business_state_change', 'expectedBusinessStateChange'], 1000),
1842
+ after: pickText(source, ['after', 'after_state', 'afterState'], 1000),
1843
+ observed: pickText(source, ['observed', 'actual'], 1000),
1844
+ dataProof: pickText(source, ['dataProof', 'data_proof', 'proof', 'domProof', 'dom_proof'], 1400),
1845
+ mongoDelta: cleanObject(source.mongoDelta || source.mongo_delta),
1846
+ artifactPaths: cleanList(source.artifactPaths || source.artifact_paths || source.artifacts, 30, 500),
1847
+ message: pickText(source, ['message', 'reason', 'summary'], 1000),
1848
+ acceptanceBlocked: source.acceptanceBlocked === true || source.acceptance_blocked === true || metadata.acceptanceBlocked === true || metadata.acceptance_blocked === true,
1849
+ routeOnly: source.routeOnly === true || source.route_only === true || source.outcome === 'route_only_pass' || source.status === 'route_probe_pass' || metadata.routeOnly === true || metadata.route_only === true,
1850
+ metadata
1851
+ });
1852
+ }
1853
+ const status = cleanText(input.businessAssertionStatus || input.outcomeLabel, 80);
1854
+ if (!assertions.length && (supportBusinessAssertionPassed(status) || supportBusinessAssertionRouteOnly(status) || supportBusinessAssertionFailed(status))) {
1855
+ assertions.push({
1856
+ assertion: proofPlan?.business_assertion || proofContract?.data_or_dom_assertion || 'business assertion',
1857
+ status,
1858
+ workflow: proofContract?.action_under_test,
1859
+ route: proofPlan?.route || gate?.issue_case.route_module,
1860
+ before: proofPlan?.before,
1861
+ action: proofPlan?.action || proofContract?.action_under_test,
1862
+ expected: proofContract?.expected_business_state_change || proofPlan?.after,
1863
+ after: proofPlan?.after,
1864
+ observed: '',
1865
+ dataProof: proofContract?.data_or_dom_assertion || proofPlan?.data_assertion || '',
1866
+ mongoDelta: {},
1867
+ artifactPaths,
1868
+ message: '',
1869
+ acceptanceBlocked: supportBusinessAssertionRouteOnly(status),
1870
+ routeOnly: supportBusinessAssertionRouteOnly(status),
1871
+ metadata: {}
1872
+ });
1873
+ }
1874
+ return assertions;
1875
+ }
1876
+
1877
+ function supportBusinessProofAssertionText(assertion: ResolveIOSupportBusinessProofAssertion): string {
1878
+ return cleanText([
1879
+ assertion.assertion,
1880
+ assertion.workflow,
1881
+ assertion.before,
1882
+ assertion.action,
1883
+ assertion.expected,
1884
+ assertion.after,
1885
+ assertion.observed,
1886
+ assertion.dataProof,
1887
+ assertion.message
1888
+ ].filter(Boolean).join(' '), 4000);
1889
+ }
1890
+
1891
+ function supportBusinessProofAssertionMatchesContract(
1892
+ assertion: ResolveIOSupportBusinessProofAssertion,
1893
+ gate: ResolveIOSupportDiagnosisGate | undefined
1894
+ ): boolean {
1895
+ if (!gate) {
1896
+ return false;
1897
+ }
1898
+ const metadata = assertion.metadata || {};
1899
+ if (metadata.supportDiagnosisProof === true
1900
+ || metadata.support_diagnosis_proof === true
1901
+ || metadata.diagnosisProofPlanMatched === true
1902
+ || metadata.diagnosis_proof_plan_matched === true
1903
+ || metadata.proofPlanMatched === true
1904
+ || metadata.proof_plan_matched === true) {
1905
+ return true;
1906
+ }
1907
+ const proofPlan = gate.proof_plan;
1908
+ const proofContract = proofPlan.business_proof_contract;
1909
+ const requiredParts = [
1910
+ proofPlan.business_assertion,
1911
+ proofPlan.after,
1912
+ proofPlan.data_assertion,
1913
+ proofContract?.expected_business_state_change,
1914
+ proofContract?.data_or_dom_assertion
1915
+ ].map((part) => cleanText(part, 1000).toLowerCase()).filter((part) => part.length >= 12);
1916
+ const assertionText = supportBusinessProofAssertionText(assertion).toLowerCase();
1917
+ if (!requiredParts.length) {
1918
+ return false;
1919
+ }
1920
+ if (requiredParts.some((part) => assertionText.includes(part))) {
1921
+ return true;
1922
+ }
1923
+ const proofWords = new Set(requiredParts.join(' ').split(/[^a-z0-9]+/g).filter((word) => word.length >= 5));
1924
+ const assertionWords = new Set(assertionText.split(/[^a-z0-9]+/g).filter((word) => word.length >= 5));
1925
+ let overlap = 0;
1926
+ for (const word of proofWords) {
1927
+ if (assertionWords.has(word)) {
1928
+ overlap += 1;
1929
+ }
1930
+ }
1931
+ return overlap >= Math.min(5, Math.max(3, Math.ceil(proofWords.size * 0.45)));
1932
+ }
1933
+
1934
+ function supportBusinessProofArtifactFingerprint(paths: string[]): string {
1935
+ const normalized = cleanList(paths, 80, 500).sort();
1936
+ return normalized.length ? hashResolveIOSupportV5Evidence({ artifacts: normalized }) : '';
1937
+ }
1938
+
1939
+ function supportBusinessProofFingerprint(
1940
+ gate: ResolveIOSupportDiagnosisGate | undefined,
1941
+ assertion: ResolveIOSupportBusinessProofAssertion | undefined,
1942
+ artifactPaths: string[] = []
1943
+ ): string {
1944
+ if (!gate || !assertion) {
1945
+ return '';
1946
+ }
1947
+ const proofPlan = gate.proof_plan;
1948
+ const proofContract = proofPlan.business_proof_contract;
1949
+ return hashResolveIOSupportV5Evidence({
1950
+ issueClass: gate.issue_class,
1951
+ proofPlan: {
1952
+ before: proofPlan.before,
1953
+ action: proofPlan.action,
1954
+ after: proofPlan.after,
1955
+ businessAssertion: proofPlan.business_assertion,
1956
+ dataAssertion: proofPlan.data_assertion,
1957
+ contractAction: proofContract?.action_under_test,
1958
+ contractExpected: proofContract?.expected_business_state_change,
1959
+ contractAssertion: proofContract?.data_or_dom_assertion
1960
+ },
1961
+ assertion: {
1962
+ assertion: assertion.assertion,
1963
+ status: assertion.status,
1964
+ workflow: assertion.workflow,
1965
+ route: assertion.route,
1966
+ before: assertion.before,
1967
+ action: assertion.action,
1968
+ expected: assertion.expected,
1969
+ after: assertion.after,
1970
+ observed: assertion.observed,
1971
+ dataProof: assertion.dataProof,
1972
+ mongoDelta: assertion.mongoDelta
1973
+ },
1974
+ artifacts: cleanList(assertion.artifactPaths?.length ? assertion.artifactPaths : artifactPaths, 80, 500).sort()
1975
+ });
1976
+ }
1977
+
1978
+ function defaultSupportBusinessProofReadinessFields(values: {
1979
+ artifactPaths?: string[];
1980
+ proofFingerprint?: string;
1981
+ artifactFingerprint?: string;
1982
+ proofFreshness?: ResolveIOSupportBusinessProofReadiness['proofFreshness'];
1983
+ } = {}): Pick<ResolveIOSupportBusinessProofReadiness, 'artifactPaths' | 'proofFingerprint' | 'artifactFingerprint' | 'proofFreshness'> {
1984
+ const artifactPaths = cleanList(values.artifactPaths, 80, 500);
1985
+ return {
1986
+ artifactPaths,
1987
+ proofFingerprint: cleanText(values.proofFingerprint, 160),
1988
+ artifactFingerprint: cleanText(values.artifactFingerprint, 160) || supportBusinessProofArtifactFingerprint(artifactPaths),
1989
+ proofFreshness: values.proofFreshness || (artifactPaths.length ? 'unknown' : 'missing')
1990
+ };
1991
+ }
1992
+
1993
+ export function evaluateResolveIOSupportBusinessProofReadiness(
1994
+ input: ResolveIOSupportBusinessProofReadinessInput = {}
1995
+ ): ResolveIOSupportBusinessProofReadiness {
1996
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(input.diagnosisGate);
1997
+ const gate = diagnosisValidation.normalized;
1998
+ const requiredEvidence = [
1999
+ 'valid SupportDiagnosisGate proof_plan',
2000
+ 'AIQaBusinessAssertion status=pass',
2001
+ 'business assertion maps to diagnosis proof_plan/business_proof_contract',
2002
+ 'artifact path for browser/data proof',
2003
+ 'DOM/data proof or Mongo delta for the expected business state change'
2004
+ ];
2005
+ if (!diagnosisValidation.valid || !gate) {
2006
+ return {
2007
+ ready: false,
2008
+ status: 'blocked',
2009
+ reason: 'support_business_proof_waiting_on_valid_diagnosis',
2010
+ blockers: diagnosisValidation.blockers.length ? diagnosisValidation.blockers : ['Valid SupportDiagnosisGate is required before business proof can accept the ticket.'],
2011
+ requiredEvidence,
2012
+ ...defaultSupportBusinessProofReadinessFields()
2013
+ };
2014
+ }
2015
+ const assertions = normalizeSupportBusinessProofAssertions(input);
2016
+ if (!assertions.length) {
2017
+ return {
2018
+ ready: false,
2019
+ status: 'missing',
2020
+ reason: 'support_business_proof_missing_aiqa_business_assertion',
2021
+ blockers: ['No AIQaBusinessAssertion was recorded for the diagnosis proof_plan.'],
2022
+ requiredEvidence,
2023
+ ...defaultSupportBusinessProofReadinessFields()
2024
+ };
2025
+ }
2026
+ const artifactPaths = Array.from(new Set(assertions.flatMap((assertion) => assertion.artifactPaths || [])));
2027
+ const artifactFingerprint = supportBusinessProofArtifactFingerprint(artifactPaths);
2028
+ const failed = assertions.find((assertion) => supportBusinessAssertionFailed(assertion.status));
2029
+ if (failed) {
2030
+ const failedFingerprint = supportBusinessProofFingerprint(gate, failed, artifactPaths);
2031
+ return {
2032
+ ready: false,
2033
+ status: 'failed',
2034
+ reason: 'support_business_proof_assertion_failed',
2035
+ blockers: [failed.message || failed.observed || failed.assertion || 'Business assertion failed.'],
2036
+ requiredEvidence,
2037
+ ...defaultSupportBusinessProofReadinessFields({
2038
+ artifactPaths,
2039
+ proofFingerprint: failedFingerprint,
2040
+ artifactFingerprint,
2041
+ proofFreshness: 'fresh'
2042
+ }),
2043
+ matchedAssertion: failed
2044
+ };
2045
+ }
2046
+ const routeOnly = assertions.find((assertion) => assertion.routeOnly || assertion.acceptanceBlocked || supportBusinessAssertionRouteOnly(assertion.status) || proofPlanLooksRouteOnly({
2047
+ before: assertion.before || '',
2048
+ action: assertion.action || '',
2049
+ after: assertion.after || assertion.expected || '',
2050
+ business_assertion: assertion.assertion || assertion.message || '',
2051
+ data_assertion: assertion.dataProof || '',
2052
+ artifact_expectation: assertion.artifactPaths.join(', ')
2053
+ }));
2054
+ if (routeOnly) {
2055
+ const routeOnlyFingerprint = supportBusinessProofFingerprint(gate, routeOnly, artifactPaths);
2056
+ return {
2057
+ ready: false,
2058
+ status: 'route_only',
2059
+ reason: 'support_business_proof_route_only_or_acceptance_blocked',
2060
+ blockers: ['Route probe, shell/screenshot, or acceptance_blocked proof cannot accept the ticket. Run the issue-class business assertion.'],
2061
+ requiredEvidence,
2062
+ ...defaultSupportBusinessProofReadinessFields({
2063
+ artifactPaths,
2064
+ proofFingerprint: routeOnlyFingerprint,
2065
+ artifactFingerprint,
2066
+ proofFreshness: 'fresh'
2067
+ }),
2068
+ matchedAssertion: routeOnly
2069
+ };
2070
+ }
2071
+ const passedAssertions = assertions.filter((assertion) => supportBusinessAssertionPassed(assertion.status));
2072
+ const matched = passedAssertions.find((assertion) => supportBusinessProofAssertionMatchesContract(assertion, gate));
2073
+ if (!matched) {
2074
+ return {
2075
+ ready: false,
2076
+ status: 'weak',
2077
+ reason: 'support_business_proof_does_not_match_diagnosis_contract',
2078
+ blockers: ['A passed assertion exists, but it does not map to the diagnosis proof_plan/business_proof_contract.'],
2079
+ requiredEvidence,
2080
+ ...defaultSupportBusinessProofReadinessFields({ artifactPaths, artifactFingerprint })
2081
+ };
2082
+ }
2083
+ const proofArtifactPaths = matched.artifactPaths.length ? matched.artifactPaths : artifactPaths;
2084
+ const proofFingerprint = supportBusinessProofFingerprint(gate, matched, proofArtifactPaths);
2085
+ const matchedArtifactFingerprint = supportBusinessProofArtifactFingerprint(proofArtifactPaths);
2086
+ const previousProofFingerprint = cleanText(input.previousProofFingerprint, 160);
2087
+ const previousArtifactFingerprint = cleanText(input.previousArtifactFingerprint, 160);
2088
+ const sameProofAsPrevious = !!previousProofFingerprint && previousProofFingerprint === proofFingerprint;
2089
+ const sameArtifactAsPrevious = !!previousArtifactFingerprint && previousArtifactFingerprint === matchedArtifactFingerprint;
2090
+ if (sameProofAsPrevious || sameArtifactAsPrevious) {
2091
+ return {
2092
+ ready: false,
2093
+ status: 'stale',
2094
+ reason: sameProofAsPrevious
2095
+ ? 'support_business_proof_same_fingerprint_as_previous'
2096
+ : 'support_business_proof_same_artifact_fingerprint_as_previous',
2097
+ blockers: ['Business proof did not produce a new proof/artifact fingerprint for this run. Rerun the issue-specific assertion and record fresh artifact proof.'],
2098
+ requiredEvidence,
2099
+ ...defaultSupportBusinessProofReadinessFields({
2100
+ artifactPaths: proofArtifactPaths,
2101
+ proofFingerprint,
2102
+ artifactFingerprint: matchedArtifactFingerprint,
2103
+ proofFreshness: sameProofAsPrevious ? 'same_as_previous' : 'stale_artifact'
2104
+ }),
2105
+ matchedAssertion: matched
2106
+ };
2107
+ }
2108
+ if (!matched.artifactPaths.length && !artifactPaths.length) {
2109
+ return {
2110
+ ready: false,
2111
+ status: 'weak',
2112
+ reason: 'support_business_proof_missing_artifact_path',
2113
+ blockers: ['Business proof must include at least one artifact path.'],
2114
+ requiredEvidence,
2115
+ ...defaultSupportBusinessProofReadinessFields({
2116
+ artifactPaths,
2117
+ proofFingerprint,
2118
+ artifactFingerprint: matchedArtifactFingerprint
2119
+ }),
2120
+ matchedAssertion: matched
2121
+ };
2122
+ }
2123
+ const hasDataOrDomProof = !!cleanText(matched.dataProof, 50)
2124
+ || Object.keys(cleanObject(matched.mongoDelta)).length > 0
2125
+ || !!cleanText(matched.observed, 80)
2126
+ || !!cleanText(matched.after, 80);
2127
+ if (!hasDataOrDomProof) {
2128
+ return {
2129
+ ready: false,
2130
+ status: 'weak',
2131
+ reason: 'support_business_proof_missing_dom_or_data_proof',
2132
+ blockers: ['Business proof must include DOM/data proof, observed result, after-state, or Mongo delta.'],
2133
+ requiredEvidence,
2134
+ ...defaultSupportBusinessProofReadinessFields({
2135
+ artifactPaths: proofArtifactPaths,
2136
+ proofFingerprint,
2137
+ artifactFingerprint: matchedArtifactFingerprint
2138
+ }),
2139
+ matchedAssertion: matched
2140
+ };
2141
+ }
2142
+ return {
2143
+ ready: true,
2144
+ status: 'passed',
2145
+ reason: 'support_business_proof_ready',
2146
+ blockers: [],
2147
+ requiredEvidence,
2148
+ ...defaultSupportBusinessProofReadinessFields({
2149
+ artifactPaths: proofArtifactPaths,
2150
+ proofFingerprint,
2151
+ artifactFingerprint: matchedArtifactFingerprint,
2152
+ proofFreshness: 'fresh'
2153
+ }),
2154
+ matchedAssertion: matched
2155
+ };
2156
+ }
2157
+
2158
+ function buildSupportClarificationQuestion(gate: ResolveIOSupportDiagnosisGate | undefined, blockers: string[]): string {
2159
+ if (gate?.issue_case.reproduction_status === 'blocked' && gate.issue_case.reproduction_blocker) {
2160
+ return `Can you provide the missing detail needed to reproduce this issue: ${gate.issue_case.reproduction_blocker}?`;
2161
+ }
2162
+ if (blockers.some((blocker) => /expected_result/.test(blocker))) {
2163
+ return 'What result did you expect to see in this workflow?';
2164
+ }
2165
+ if (blockers.some((blocker) => /observed_result/.test(blocker))) {
2166
+ return 'What exact result are you seeing instead, including the affected record or screen if possible?';
2167
+ }
2168
+ if (blockers.some((blocker) => /account_customer_context/.test(blocker))) {
2169
+ return 'Which customer/account/user context should we use to reproduce this issue?';
2170
+ }
2171
+ return 'Can you send one concrete example record, screen, or action path where this issue still occurs?';
2172
+ }
2173
+
2174
+ function buildResolveIOSupportHumanReviewPacket(input: {
2175
+ reviewType: ResolveIOSupportHumanReviewType;
2176
+ title: string;
2177
+ summary?: string;
2178
+ primaryAction: string;
2179
+ question?: string;
2180
+ customerFacingDraftAllowed?: boolean;
2181
+ customerSendAllowed?: boolean;
2182
+ requiresHumanApproval?: boolean;
2183
+ safety?: ResolveIOSupportHumanReviewPacket['safety'];
2184
+ reason?: string;
2185
+ blockers?: any[];
2186
+ requiredEvidence?: any[];
2187
+ evidenceRefs?: any[];
2188
+ nextCommands?: any[];
2189
+ forbiddenActions?: any[];
2190
+ costRisk?: ResolveIOSupportHumanReviewPacket['costRisk'];
2191
+ now?: Date | string;
2192
+ }): ResolveIOSupportHumanReviewPacket {
2193
+ return {
2194
+ reviewType: input.reviewType,
2195
+ title: cleanText(input.title, 200) || 'Review Support Runner Action',
2196
+ summary: cleanText(input.summary || input.reason, 1000),
2197
+ primaryAction: cleanText(input.primaryAction, 160) || 'review_support_runner_action',
2198
+ question: cleanText(input.question, 1000) || undefined,
2199
+ customerFacingDraftAllowed: input.customerFacingDraftAllowed === true,
2200
+ customerSendAllowed: false,
2201
+ requiresHumanApproval: input.requiresHumanApproval !== false,
2202
+ safety: input.safety || 'internal_hold',
2203
+ reason: cleanText(input.reason, 500),
2204
+ blockers: cleanList(input.blockers, 20, 500),
2205
+ requiredEvidence: cleanList(input.requiredEvidence, 30, 500),
2206
+ evidenceRefs: cleanList(input.evidenceRefs, 40, 500),
2207
+ nextCommands: cleanList(input.nextCommands, 20, 200),
2208
+ forbiddenActions: Array.from(new Set([
2209
+ 'Do not send customer email without explicit human approval.',
2210
+ ...cleanList(input.forbiddenActions, 20, 500)
2211
+ ])),
2212
+ costRisk: input.costRisk || 'small_model_or_qa',
2213
+ createdAt: isoNow(input.now)
2214
+ };
2215
+ }
2216
+
2217
+ function supportAutonomousReviewTypeForAction(action: ResolveIOSupportV5AutonomousNextAction): ResolveIOSupportHumanReviewType {
2218
+ switch (action) {
2219
+ case 'run_diagnosis_gate':
2220
+ return 'diagnosis_gate';
2221
+ case 'ask_customer_clarification':
2222
+ return 'customer_clarification';
2223
+ case 'run_owner_scoped_repair':
2224
+ case 'revise_diagnosis_scope':
2225
+ return 'owner_scoped_repair';
2226
+ case 'run_business_proof_qa':
2227
+ return 'business_proof_qa';
2228
+ case 'repair_release_hotfix_first':
2229
+ case 'ready_for_release_gate':
2230
+ return 'release_hotfix';
2231
+ case 'draft_customer_reply':
2232
+ return 'customer_resolution_reply';
2233
+ case 'collect_new_evidence':
2234
+ return 'new_evidence';
2235
+ case 'repair_infra_only':
2236
+ case 'park_manual':
2237
+ default:
2238
+ return 'internal_hold';
2239
+ }
2240
+ }
2241
+
2242
+ export function decideResolveIOSupportCustomerReplyPolicy(input: ResolveIOSupportCustomerReplyPolicyInput = {}): ResolveIOSupportCustomerReplyPolicy {
2243
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(input.diagnosisGate);
2244
+ const gate = diagnosisValidation.normalized;
2245
+ const confidenceLevel = normalizeSupportConfidenceLevel(input.confidence);
2246
+ const shouldBlockConfidence = input.confidence?.shouldBlockPr === true
2247
+ || input.confidence?.should_block_pr === true
2248
+ || input.confidence?.blocked === true;
2249
+ const businessProofReadiness = evaluateResolveIOSupportBusinessProofReadiness({
2250
+ diagnosisGate: input.diagnosisGate,
2251
+ outcomeLabel: input.outcomeLabel,
2252
+ businessAssertionStatus: input.businessAssertionStatus,
2253
+ businessAssertions: input.businessAssertions,
2254
+ businessProofArtifacts: input.businessProofArtifacts,
2255
+ previousProofFingerprint: input.previousProofFingerprint,
2256
+ previousArtifactFingerprint: input.previousArtifactFingerprint
2257
+ });
2258
+ const artifactCount = businessProofReadiness.artifactPaths.length;
2259
+ const unresolvedBlockers = cleanList(input.unresolvedBlockers, 20, 500);
2260
+ const releaseBlocked = supportReleaseLooksBlocked(input.releaseStatus);
2261
+ const requiredEvidence = [
2262
+ 'valid SupportDiagnosisGate',
2263
+ 'high confidence with shouldBlockPr=false',
2264
+ 'accepted outcome or passed business assertion',
2265
+ 'business proof contract and artifact evidence',
2266
+ 'no unresolved blocker or release blocker'
2267
+ ];
2268
+ if (!diagnosisValidation.valid) {
2269
+ const canAskCustomer = gate?.issue_case.reproduction_status === 'blocked'
2270
+ || diagnosisValidation.blockers.some((blocker) => /expected_result|observed_result|account_customer_context/.test(blocker));
2271
+ if (canAskCustomer) {
2272
+ const clarificationQuestion = buildSupportClarificationQuestion(gate, diagnosisValidation.blockers);
2273
+ return {
2274
+ action: 'ask_clarification',
2275
+ canDraftCustomerReply: true,
2276
+ canSendCustomerReply: false,
2277
+ confidenceLevel,
2278
+ safety: 'needs_clarification',
2279
+ reason: 'support_reply_waiting_on_customer_reproduction_detail',
2280
+ requiredEvidence,
2281
+ clarificationQuestion,
2282
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2283
+ reviewType: 'customer_clarification',
2284
+ title: 'Review Customer Clarification',
2285
+ summary: 'The runner needs one customer detail before it can reproduce or continue safely.',
2286
+ primaryAction: 'review_customer_clarification',
2287
+ question: clarificationQuestion,
2288
+ customerFacingDraftAllowed: true,
2289
+ safety: 'needs_clarification',
2290
+ reason: 'support_reply_waiting_on_customer_reproduction_detail',
2291
+ blockers: diagnosisValidation.blockers,
2292
+ requiredEvidence,
2293
+ nextCommands: ['edit_clarification_question', 'send_after_human_review', 'park_ticket_until_customer_reply'],
2294
+ forbiddenActions: ['Do not run repair from a guessed reproduction path.'],
2295
+ costRisk: 'release_or_customer_send'
2296
+ })
2297
+ };
2298
+ }
2299
+ return {
2300
+ action: 'hold_internal',
2301
+ canDraftCustomerReply: false,
2302
+ canSendCustomerReply: false,
2303
+ confidenceLevel,
2304
+ safety: 'internal_hold',
2305
+ reason: 'support_reply_blocked_until_diagnosis_gate_validates',
2306
+ requiredEvidence,
2307
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2308
+ reviewType: 'internal_hold',
2309
+ title: 'Complete Diagnosis Gate',
2310
+ summary: 'Customer reply is blocked until the diagnosis gate validates.',
2311
+ primaryAction: 'run_support_v5_read_only_diagnosis_gate',
2312
+ safety: 'internal_hold',
2313
+ reason: 'support_reply_blocked_until_diagnosis_gate_validates',
2314
+ blockers: diagnosisValidation.blockers,
2315
+ requiredEvidence,
2316
+ nextCommands: ['retrieve_similar_tickets_and_commits', 'run_reproduction_or_classification_probe', 'write_support_diagnosis_gate_json'],
2317
+ forbiddenActions: ['Do not draft a resolution reply before root-cause diagnosis validates.'],
2318
+ costRisk: 'expensive_model'
2319
+ })
2320
+ };
2321
+ }
2322
+ if (shouldBlockConfidence || confidenceLevel !== 'high') {
2323
+ return {
2324
+ action: 'hold_internal',
2325
+ canDraftCustomerReply: false,
2326
+ canSendCustomerReply: false,
2327
+ confidenceLevel,
2328
+ safety: 'internal_hold',
2329
+ reason: shouldBlockConfidence ? 'support_reply_blocked_by_confidence_gate' : 'support_reply_requires_high_confidence',
2330
+ requiredEvidence,
2331
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2332
+ reviewType: 'internal_hold',
2333
+ title: 'Review Confidence Gate',
2334
+ summary: 'Customer reply is blocked until confidence is high and the confidence gate is not blocking.',
2335
+ primaryAction: 'review_support_confidence_evidence',
2336
+ safety: 'internal_hold',
2337
+ reason: shouldBlockConfidence ? 'support_reply_blocked_by_confidence_gate' : 'support_reply_requires_high_confidence',
2338
+ requiredEvidence,
2339
+ nextCommands: ['inspect_confidence_basis', 'collect_missing_business_or_release_evidence'],
2340
+ forbiddenActions: ['Do not draft a customer resolution from medium or low confidence.'],
2341
+ costRisk: 'free_or_deterministic'
2342
+ })
2343
+ };
2344
+ }
2345
+ if (unresolvedBlockers.length) {
2346
+ return {
2347
+ action: 'hold_internal',
2348
+ canDraftCustomerReply: false,
2349
+ canSendCustomerReply: false,
2350
+ confidenceLevel,
2351
+ safety: 'internal_hold',
2352
+ reason: 'support_reply_blocked_by_unresolved_runner_blocker',
2353
+ requiredEvidence,
2354
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2355
+ reviewType: 'internal_hold',
2356
+ title: 'Resolve Runner Blocker',
2357
+ summary: 'Customer reply is blocked by unresolved runner blockers.',
2358
+ primaryAction: 'review_support_runner_blocker',
2359
+ safety: 'internal_hold',
2360
+ reason: 'support_reply_blocked_by_unresolved_runner_blocker',
2361
+ blockers: unresolvedBlockers,
2362
+ requiredEvidence,
2363
+ nextCommands: ['classify_blocker', 'collect_new_evidence_or_repair_smallest_gate'],
2364
+ forbiddenActions: ['Do not send customer status as resolved while blockers remain.'],
2365
+ costRisk: 'small_model_or_qa'
2366
+ })
2367
+ };
2368
+ }
2369
+ if (releaseBlocked) {
2370
+ return {
2371
+ action: 'hold_internal',
2372
+ canDraftCustomerReply: false,
2373
+ canSendCustomerReply: false,
2374
+ confidenceLevel,
2375
+ safety: 'internal_hold',
2376
+ reason: 'support_reply_blocked_until_release_or_hotfix_gate_finishes',
2377
+ requiredEvidence,
2378
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2379
+ reviewType: 'release_hotfix',
2380
+ title: 'Finish Release Or Hotfix Gate',
2381
+ summary: 'Business proof exists, but the customer reply is blocked until release or hotfix evidence is complete.',
2382
+ primaryAction: 'repair_release_hotfix_first',
2383
+ safety: 'internal_hold',
2384
+ reason: 'support_reply_blocked_until_release_or_hotfix_gate_finishes',
2385
+ blockers: [input.releaseStatus],
2386
+ requiredEvidence,
2387
+ nextCommands: ['record_hotfix_evidence', 'commit_and_push_hotfix_to_github', 'rerun_release_gate_once'],
2388
+ forbiddenActions: ['Do not tell the customer the fix is live before release evidence passes.'],
2389
+ costRisk: 'release_or_customer_send'
2390
+ })
2391
+ };
2392
+ }
2393
+ if (!businessProofReadiness.ready) {
2394
+ const replyReason = businessProofReadiness.reason === 'support_business_proof_route_only_or_acceptance_blocked'
2395
+ ? 'support_reply_rejects_route_only_business_proof'
2396
+ : 'support_reply_requires_business_assertion_pass';
2397
+ const proofRequiredEvidence = Array.from(new Set([
2398
+ ...requiredEvidence,
2399
+ ...businessProofReadiness.requiredEvidence
2400
+ ]));
2401
+ return {
2402
+ action: 'hold_internal',
2403
+ canDraftCustomerReply: false,
2404
+ canSendCustomerReply: false,
2405
+ confidenceLevel,
2406
+ safety: 'internal_hold',
2407
+ reason: replyReason,
2408
+ requiredEvidence: proofRequiredEvidence,
2409
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2410
+ reviewType: 'business_proof_qa',
2411
+ title: 'Run Business Proof QA',
2412
+ summary: 'Customer reply is blocked until the issue-specific before/action/after assertion passes.',
2413
+ primaryAction: 'run_support_v5_business_proof_qa_row',
2414
+ safety: 'internal_hold',
2415
+ reason: replyReason,
2416
+ blockers: businessProofReadiness.blockers,
2417
+ requiredEvidence: proofRequiredEvidence,
2418
+ evidenceRefs: businessProofReadiness.artifactPaths,
2419
+ nextCommands: ['execute_issue_class_probe', 'record_before_action_after_artifacts', 'write_aiqa_business_assertion'],
2420
+ forbiddenActions: ['Route probe pass remains route evidence only.'],
2421
+ costRisk: 'small_model_or_qa'
2422
+ })
2423
+ };
2424
+ }
2425
+ if (!gate?.proof_plan.business_proof_contract || artifactCount < 1) {
2426
+ return {
2427
+ action: 'hold_internal',
2428
+ canDraftCustomerReply: false,
2429
+ canSendCustomerReply: false,
2430
+ confidenceLevel,
2431
+ safety: 'internal_hold',
2432
+ reason: 'support_reply_requires_business_proof_contract_artifact',
2433
+ requiredEvidence,
2434
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2435
+ reviewType: 'business_proof_qa',
2436
+ title: 'Attach Business Proof Artifact',
2437
+ summary: 'Customer reply is blocked until the business proof contract and artifact are attached.',
2438
+ primaryAction: 'record_business_proof_artifact',
2439
+ safety: 'internal_hold',
2440
+ reason: 'support_reply_requires_business_proof_contract_artifact',
2441
+ requiredEvidence,
2442
+ evidenceRefs: businessProofReadiness.artifactPaths,
2443
+ nextCommands: ['attach_business_proof_contract', 'attach_artifact_path', 'rerun_reply_policy'],
2444
+ forbiddenActions: ['Do not draft a resolution reply without a proof artifact.'],
2445
+ costRisk: 'free_or_deterministic'
2446
+ })
2447
+ };
2448
+ }
2449
+ return {
2450
+ action: 'draft_resolution_reply',
2451
+ canDraftCustomerReply: true,
2452
+ canSendCustomerReply: false,
2453
+ confidenceLevel,
2454
+ safety: 'safe_to_draft',
2455
+ reason: 'support_reply_resolution_draft_allowed_after_business_proof',
2456
+ requiredEvidence,
2457
+ humanReviewPacket: buildResolveIOSupportHumanReviewPacket({
2458
+ reviewType: 'customer_resolution_reply',
2459
+ title: 'Review Customer Resolution Reply',
2460
+ summary: `Business proof is ready for ${gate.issue_class}; draft a customer-facing resolution for human review.`,
2461
+ primaryAction: 'review_customer_reply',
2462
+ customerFacingDraftAllowed: true,
2463
+ safety: 'safe_to_draft',
2464
+ reason: 'support_reply_resolution_draft_allowed_after_business_proof',
2465
+ requiredEvidence,
2466
+ evidenceRefs: businessProofReadiness.artifactPaths,
2467
+ nextCommands: ['summarize_business_proof', 'draft_customer_reply_for_human_review'],
2468
+ forbiddenActions: ['Draft only; do not send automatically.'],
2469
+ costRisk: 'release_or_customer_send'
2470
+ }),
2471
+ draftBasis: {
2472
+ issueClass: gate.issue_class,
2473
+ businessProof: gate.proof_plan.business_proof_contract.expected_business_state_change,
2474
+ artifactCount
2475
+ }
2476
+ };
2477
+ }
2478
+
2479
+ export function buildResolveIOSupportIssueClassProbes(value: any): ResolveIOSupportIssueClassProbe[] {
2480
+ const validation = validateResolveIOSupportDiagnosisGate(value);
2481
+ const gate = validation.normalized || normalizeResolveIOSupportDiagnosisGate(value);
2482
+ if (!gate) {
2483
+ return [];
2484
+ }
2485
+ const route = gate.proof_plan.route || gate.issue_case.route_module;
2486
+ const proofContract = gate.proof_plan.business_proof_contract;
2487
+ const proof = proofContract?.expected_business_state_change || gate.proof_plan.business_assertion;
2488
+ const assertion = proofContract?.data_or_dom_assertion || gate.proof_plan.data_assertion || gate.proof_plan.after;
2489
+ const contractArtifacts = cleanList(proofContract?.proof_artifacts, 8, 160);
2490
+ const common = {
2491
+ issue_class: gate.issue_class,
2492
+ probe_type: 'issue_class_probe' as ResolveIOSupportV5StepType,
2493
+ route,
2494
+ state_transition: {
2495
+ before: gate.proof_plan.before || gate.proof_plan.before_state_unavailable_reason || proofContract?.setup_state || '',
2496
+ action: gate.proof_plan.action || proofContract?.action_under_test || '',
2497
+ after: gate.proof_plan.after || proofContract?.expected_business_state_change || '',
2498
+ assertion: assertion || gate.proof_plan.business_assertion || ''
2499
+ },
2500
+ acceptance_gate: 'aiqa_business_assertion' as const,
2501
+ blocks_acceptance_without_business_assertion: true
2502
+ };
2503
+ const map: Record<ResolveIOSupportIssueClass, {
2504
+ action: string;
2505
+ expected: string;
2506
+ failureClass: ResolveIOSupportV5FailureClass;
2507
+ requiredArtifacts: string[];
2508
+ }> = {
2509
+ no_op_submit: {
2510
+ action: `Submit the customer action on ${route || 'the affected screen'} and assert a persisted state change or explicit validation message.`,
2511
+ expected: proof || 'Before/action/after proof shows the submit is no longer a no-op.',
2512
+ failureClass: 'business',
2513
+ requiredArtifacts: ['before form state', 'submit action trace', 'post-submit DOM/data proof', 'method/network result']
2514
+ },
2515
+ missing_wrong_data: {
2516
+ action: `Load the named record/list on ${route || 'the affected route'} and compare visible data to the expected persisted source.`,
2517
+ expected: proof || 'Visible data and persisted data match the customer expectation.',
2518
+ failureClass: 'business',
2519
+ requiredArtifacts: ['expected source record', 'visible DOM/data snapshot', 'persisted data comparison']
2520
+ },
2521
+ filter_query_mismatch: {
2522
+ action: 'Apply the reported filter/query inputs and assert the returned rows/counts match the expected dataset.',
2523
+ expected: proof || 'Filter/query output contains the correct included rows and excludes the wrong rows.',
2524
+ failureClass: 'business',
2525
+ requiredArtifacts: ['filter input trace', 'included/excluded row proof', 'query or publication result proof']
2526
+ },
2527
+ invoice_pdf_export: {
2528
+ action: 'Generate the invoice/PDF/export from the affected route and inspect the downloaded/generated artifact.',
2529
+ expected: proof || 'Generated artifact contains the expected customer-visible rows, totals, or fields.',
2530
+ failureClass: 'business',
2531
+ requiredArtifacts: ['export trigger trace', 'generated file artifact path', 'parsed artifact content proof']
2532
+ },
2533
+ upload_import: {
2534
+ action: 'Run the upload/import workflow with a representative file and assert parsed plus persisted results.',
2535
+ expected: proof || 'Import shows a success result and persisted rows/counts changed as expected.',
2536
+ failureClass: 'business',
2537
+ requiredArtifacts: ['input file fixture', 'import result message', 'persisted row/count delta']
2538
+ },
2539
+ route_auth_hydration: {
2540
+ action: 'Open the route as the affected user and assert authenticated hydration reaches the functional screen, not a shell.',
2541
+ expected: proof || 'Route hydrates with the required controls/data for the affected account.',
2542
+ failureClass: 'route',
2543
+ requiredArtifacts: ['auth context', 'hydrated DOM proof', 'console/network error log']
2544
+ },
2545
+ slow_query_performance: {
2546
+ action: 'Run the reported query/workflow with timing/log evidence before and after the fix.',
2547
+ expected: proof || 'Performance evidence shows the slow path improved without changing results.',
2548
+ failureClass: 'business',
2549
+ requiredArtifacts: ['before timing', 'after timing', 'result equivalence proof', 'query/log trace']
2550
+ }
2551
+ };
2552
+ const selected = map[gate.issue_class];
2553
+ const requiredArtifacts = Array.from(new Set([
2554
+ ...contractArtifacts,
2555
+ ...selected.requiredArtifacts
2556
+ ]));
2557
+ return [{
2558
+ ...common,
2559
+ failure_class: selected.failureClass,
2560
+ objective: `Issue-class probe for ${gate.issue_class}: ${gate.issue_case.customer_complaint}`,
2561
+ action: proofContract
2562
+ ? `${proofContract.action_under_test} ${selected.action}`
2563
+ : selected.action,
2564
+ expected_evidence: `${selected.expected}${assertion ? ` Assertion: ${assertion}.` : ''}${requiredArtifacts.length ? ` Required artifacts: ${requiredArtifacts.join(', ')}.` : ''}`.trim(),
2565
+ expected_business_proof: proof || gate.proof_plan.business_assertion,
2566
+ required_artifacts: requiredArtifacts
2567
+ }];
2568
+ }
2569
+
2570
+ export function hashResolveIOSupportV5Evidence(value: any): string {
2571
+ const raw = typeof value === 'string' ? value : JSON.stringify(value || {});
2572
+ const normalized = cleanText(raw, 8000)
2573
+ .toLowerCase()
2574
+ .replace(/[a-f0-9]{16,}/g, '<id>')
2575
+ .replace(/\b\d{2,}\b/g, '<n>');
2576
+ let hash = 0;
2577
+ for (let index = 0; index < normalized.length; index += 1) {
2578
+ hash = ((hash << 5) - hash + normalized.charCodeAt(index)) | 0;
2579
+ }
2580
+ return `ev-${Math.abs(hash).toString(36) || '0'}`;
2581
+ }
2582
+
2583
+ export function decideResolveIOSupportV5RepeatedFailureStop(input: ResolveIOSupportV5RepeatStopInput): ResolveIOSupportV5RepeatStopDecision {
2584
+ const failureClass = cleanText(input.failureClass || 'unknown', 80).toLowerCase();
2585
+ const blockerFingerprint = fingerprintResolveIOSupportV5Blocker(input.blocker || '');
2586
+ const evidenceHash = cleanText(input.evidenceHash, 80) || hashResolveIOSupportV5Evidence(input.evidence || input.blocker || '');
2587
+ const limit = Math.max(1, Number(input.limit || 2) || 2);
2588
+ if (input.ignoreInfra !== false && /^(infra|compile)$/.test(failureClass)) {
2589
+ return {
2590
+ shouldStop: false,
2591
+ repeatedCount: 0,
2592
+ failureClass,
2593
+ blockerFingerprint,
2594
+ evidenceHash,
2595
+ reason: 'support_v5_infra_failures_do_not_count_as_product_repair_loops'
2596
+ };
2597
+ }
2598
+ const latestRecord = (input.history || [])[(input.history || []).length - 1];
2599
+ const managerDecision = decideResolveIOAIManagerPolicy({
2600
+ history: (input.history || []).map((entry) => ({
2601
+ outcome: entry.outcome,
2602
+ lane: entry.lane,
2603
+ stepType: entry.stepType,
2604
+ failureClass: entry.failureClass,
2605
+ blocker: entry.blocker || entry.summary,
2606
+ evidenceHash: entry.evidenceHash,
2607
+ changedFiles: entry.changedFiles,
2608
+ artifactPaths: entry.artifactPaths,
2609
+ summary: entry.summary,
2610
+ recordedAt: entry.recordedAt
2611
+ })),
2612
+ current: {
2613
+ outcome: 'needs_repair',
2614
+ lane: latestRecord?.lane,
2615
+ stepType: latestRecord?.stepType,
2616
+ failureClass,
2617
+ blocker: input.blocker,
2618
+ evidenceHash,
2619
+ changedFiles: cleanList(input.changedFiles, 40, 500),
2620
+ artifactPaths: cleanList(input.artifactPaths, 40, 500),
2621
+ summary: input.blocker
2622
+ },
2623
+ maxSameFailureRepeats: limit,
2624
+ maxPingPongTransitions: 3,
2625
+ infraFailureClasses: input.ignoreInfra !== false ? ['infra', 'compile'] : []
2626
+ });
2627
+ if (managerDecision.action === 'retry_infra') {
2628
+ return {
2629
+ shouldStop: false,
2630
+ repeatedCount: 0,
2631
+ failureClass,
2632
+ blockerFingerprint,
2633
+ evidenceHash,
2634
+ reason: 'support_v5_infra_failures_do_not_count_as_product_repair_loops'
2635
+ };
2636
+ }
2637
+ if (managerDecision.action === 'park_ping_pong') {
2638
+ return {
2639
+ shouldStop: true,
2640
+ repeatedCount: managerDecision.pingPongCount,
2641
+ failureClass,
2642
+ blockerFingerprint,
2643
+ evidenceHash,
2644
+ newEvidence: managerDecision.newEvidence,
2645
+ materialEvidence: managerDecision.materialEvidence,
2646
+ evidenceStrength: managerDecision.evidenceStrength,
2647
+ evidenceSignals: managerDecision.evidenceSignals,
2648
+ reason: 'support_v5_ping_pong_failure_loop'
2649
+ };
2650
+ }
2651
+ if (managerDecision.action === 'park_repeated_failure' && managerDecision.newEvidence && !managerDecision.materialEvidence) {
2652
+ return {
2653
+ shouldStop: true,
2654
+ repeatedCount: managerDecision.sameFailureCount,
2655
+ failureClass,
2656
+ blockerFingerprint,
2657
+ evidenceHash,
2658
+ newEvidence: managerDecision.newEvidence,
2659
+ materialEvidence: managerDecision.materialEvidence,
2660
+ evidenceStrength: managerDecision.evidenceStrength,
2661
+ evidenceSignals: managerDecision.evidenceSignals,
2662
+ reason: 'support_v5_same_failure_with_weak_evidence'
2663
+ };
2664
+ }
2665
+ if (managerDecision.newEvidence && managerDecision.materialEvidence) {
2666
+ return {
2667
+ shouldStop: false,
2668
+ repeatedCount: managerDecision.sameFailureCount,
2669
+ failureClass,
2670
+ blockerFingerprint,
2671
+ evidenceHash,
2672
+ newEvidence: managerDecision.newEvidence,
2673
+ materialEvidence: managerDecision.materialEvidence,
2674
+ evidenceStrength: managerDecision.evidenceStrength,
2675
+ evidenceSignals: managerDecision.evidenceSignals,
2676
+ reason: 'support_v5_retry_allowed_material_evidence'
2677
+ };
2678
+ }
2679
+ if (managerDecision.action === 'park_repeated_failure') {
2680
+ return {
2681
+ shouldStop: true,
2682
+ repeatedCount: managerDecision.sameFailureCount,
2683
+ failureClass,
2684
+ blockerFingerprint,
2685
+ evidenceHash,
2686
+ newEvidence: managerDecision.newEvidence,
2687
+ materialEvidence: managerDecision.materialEvidence,
2688
+ evidenceStrength: managerDecision.evidenceStrength,
2689
+ evidenceSignals: managerDecision.evidenceSignals,
2690
+ reason: 'support_v5_same_failure_class_without_new_evidence'
2691
+ };
2692
+ }
2693
+ let repeatedCount = 0;
2694
+ for (let index = (input.history || []).length - 1; index >= 0; index -= 1) {
2695
+ const item = input.history[index];
2696
+ if (item.outcome === 'pass' || item.outcome === 'ready_for_merge') {
2697
+ break;
2698
+ }
2699
+ const itemFailureClass = cleanText(item.failureClass || 'unknown', 80).toLowerCase();
2700
+ const itemBlockerFingerprint = cleanText(item.blockerFingerprint, 80)
2701
+ || fingerprintResolveIOSupportV5Blocker(item.blocker || item.summary || '');
2702
+ const itemEvidenceHash = cleanText(item.evidenceHash, 80)
2703
+ || hashResolveIOSupportV5Evidence(item.artifactPaths || item.blocker || item.summary || '');
2704
+ if (itemFailureClass !== failureClass || itemBlockerFingerprint !== blockerFingerprint || itemEvidenceHash !== evidenceHash) {
2705
+ break;
2706
+ }
2707
+ repeatedCount += 1;
2708
+ }
2709
+ return {
2710
+ shouldStop: repeatedCount >= limit,
2711
+ repeatedCount,
2712
+ failureClass,
2713
+ blockerFingerprint,
2714
+ evidenceHash,
2715
+ newEvidence: managerDecision.newEvidence,
2716
+ materialEvidence: managerDecision.materialEvidence,
2717
+ evidenceStrength: managerDecision.evidenceStrength,
2718
+ evidenceSignals: managerDecision.evidenceSignals,
2719
+ reason: repeatedCount >= limit
2720
+ ? 'support_v5_same_failure_class_without_new_evidence'
2721
+ : 'support_v5_retry_allowed_new_or_below_repeat_limit'
2722
+ };
2723
+ }
2724
+
2725
+ export function evaluateResolveIOSupportEvidenceFreshness(input: {
2726
+ history?: ResolveIOSupportV5StepRecord[];
2727
+ failureClass?: ResolveIOSupportV5FailureClass | string;
2728
+ blocker?: any;
2729
+ evidence?: any;
2730
+ evidenceHash?: string;
2731
+ changedFiles?: any;
2732
+ artifactPaths?: any;
2733
+ lane?: ResolveIOSupportV5Lane | string;
2734
+ stepType?: ResolveIOSupportV5StepType | string;
2735
+ limit?: number;
2736
+ ignoreInfra?: boolean;
2737
+ }): ResolveIOSupportEvidenceFreshness {
2738
+ const history = Array.isArray(input.history) ? input.history : [];
2739
+ const latest = history.length ? history[history.length - 1] : undefined;
2740
+ const rawFailureClass = cleanText(input.failureClass || latest?.failureClass, 80).toLowerCase();
2741
+ const failureClass = rawFailureClass || 'unknown';
2742
+ const blocker = cleanText(input.blocker || latest?.blocker || latest?.summary, 1200);
2743
+ const changedFiles = cleanList(input.changedFiles || latest?.changedFiles, 40, 500);
2744
+ const artifactPaths = cleanList(input.artifactPaths || latest?.artifactPaths, 40, 500);
2745
+ const explicitEvidenceHash = cleanText(input.evidenceHash || latest?.evidenceHash, 120);
2746
+ const evidenceHash = explicitEvidenceHash || hashResolveIOSupportV5Evidence(input.evidence || artifactPaths || blocker || latest?.summary || '');
2747
+ const blockerFingerprint = fingerprintResolveIOSupportV5Blocker(blocker || latest?.summary || '');
2748
+ const missing = !history.length && !rawFailureClass && !blocker && !explicitEvidenceHash && !artifactPaths.length && !changedFiles.length;
2749
+ if (missing) {
2750
+ return {
2751
+ status: 'missing',
2752
+ failureClass: '',
2753
+ blockerFingerprint: '',
2754
+ evidenceHash: '',
2755
+ sameFailureCount: 0,
2756
+ pingPongCount: 0,
2757
+ newEvidence: false,
2758
+ materialEvidence: false,
2759
+ evidenceStrength: 'none',
2760
+ evidenceSignals: [],
2761
+ loopBudgetShouldReset: false,
2762
+ canRetry: true,
2763
+ mustCollectNewEvidence: false,
2764
+ productRepairFailure: false,
2765
+ reason: 'support_evidence_freshness_waiting_for_first_failure_or_proof',
2766
+ requiredResetEvidence: [
2767
+ 'current blocker fingerprint',
2768
+ 'evidence hash',
2769
+ 'artifact path when available'
2770
+ ],
2771
+ changedFiles,
2772
+ artifactPaths
2773
+ };
2774
+ }
2775
+ const current = {
2776
+ outcome: 'needs_repair',
2777
+ lane: cleanText(input.lane || latest?.lane, 80) || latest?.lane,
2778
+ stepType: cleanText(input.stepType || latest?.stepType, 80) || latest?.stepType,
2779
+ failureClass,
2780
+ blocker,
2781
+ evidenceHash,
2782
+ changedFiles,
2783
+ artifactPaths,
2784
+ summary: blocker
2785
+ };
2786
+ const policy = decideResolveIOAIManagerPolicy({
2787
+ history: history.map((entry) => ({
2788
+ outcome: entry.outcome,
2789
+ lane: entry.lane,
2790
+ stepType: entry.stepType,
2791
+ failureClass: entry.failureClass,
2792
+ blocker: entry.blocker || entry.summary,
2793
+ evidenceHash: entry.evidenceHash,
2794
+ changedFiles: entry.changedFiles,
2795
+ artifactPaths: entry.artifactPaths,
2796
+ summary: entry.summary,
2797
+ recordedAt: entry.recordedAt
2798
+ })),
2799
+ current,
2800
+ maxSameFailureRepeats: Math.max(1, Number(input.limit || 2) || 2),
2801
+ maxPingPongTransitions: 3,
2802
+ infraFailureClasses: input.ignoreInfra !== false ? ['infra', 'compile'] : []
2803
+ });
2804
+ let status: ResolveIOSupportEvidenceFreshnessStatus = 'retry_below_limit';
2805
+ if (policy.action === 'retry_infra') {
2806
+ status = 'infra_ignored';
2807
+ }
2808
+ else if (policy.action === 'park_ping_pong') {
2809
+ status = 'ping_pong';
2810
+ }
2811
+ else if (policy.action === 'park_repeated_failure') {
2812
+ status = policy.newEvidence ? 'weak_evidence' : 'stale_repeated';
2813
+ }
2814
+ else if (policy.newEvidence && policy.materialEvidence) {
2815
+ status = 'material_evidence';
2816
+ }
2817
+ else if (policy.newEvidence && !policy.materialEvidence) {
2818
+ status = 'weak_evidence';
2819
+ }
2820
+ else if (policy.loopBudgetShouldReset || policy.reason === 'manager_policy_new_failure_or_lane') {
2821
+ status = 'fresh';
2822
+ }
2823
+ const mustCollectNewEvidence = policy.action === 'park_repeated_failure'
2824
+ || policy.action === 'park_ping_pong';
2825
+ return {
2826
+ status,
2827
+ failureClass: policy.failureClass,
2828
+ blockerFingerprint: blockerFingerprint || policy.blockerFingerprint,
2829
+ evidenceHash: policy.evidenceHash,
2830
+ sameFailureCount: policy.sameFailureCount,
2831
+ pingPongCount: policy.pingPongCount,
2832
+ newEvidence: policy.newEvidence,
2833
+ materialEvidence: policy.materialEvidence,
2834
+ evidenceStrength: policy.evidenceStrength,
2835
+ evidenceSignals: policy.evidenceSignals,
2836
+ loopBudgetShouldReset: policy.loopBudgetShouldReset,
2837
+ canRetry: !mustCollectNewEvidence,
2838
+ mustCollectNewEvidence,
2839
+ productRepairFailure: policy.productRepairFailure,
2840
+ reason: policy.reason,
2841
+ requiredResetEvidence: Array.from(new Set([
2842
+ ...cleanList(policy.recoveryPlan.loopResetEvidence, 12, 500),
2843
+ ...cleanList(policy.recoveryAction?.successCriteria, 12, 500)
2844
+ ])).slice(0, 18),
2845
+ changedFiles,
2846
+ artifactPaths
2847
+ };
2848
+ }
2849
+
2850
+ export function buildResolveIOSupportContinuationProofCheckpoint(input: {
2851
+ action: ResolveIOSupportV5AutonomousNextAction;
2852
+ reason?: any;
2853
+ evidenceFreshness?: ResolveIOSupportEvidenceFreshness;
2854
+ requiredEvidence?: any;
2855
+ requiredResetEvidence?: any;
2856
+ blocksProductRepair?: boolean;
2857
+ }): ResolveIOSupportContinuationProofCheckpoint {
2858
+ const evidenceFreshness = input.evidenceFreshness;
2859
+ const action = input.action;
2860
+ const requiredEvidence = cleanList(input.requiredEvidence, 20, 500);
2861
+ const requiredResetEvidence = Array.from(new Set([
2862
+ ...cleanList(input.requiredResetEvidence, 20, 500),
2863
+ ...cleanList(evidenceFreshness?.requiredResetEvidence, 20, 500),
2864
+ ...(action === 'run_diagnosis_gate'
2865
+ ? [
2866
+ 'accepted SupportDiagnosisGate with reproduced/blocked issue case',
2867
+ 'bounded owner_files and before/action/after proof_plan'
2868
+ ]
2869
+ : []),
2870
+ ...(action === 'collect_new_evidence'
2871
+ ? [
2872
+ 'fresh issue-class probe artifact',
2873
+ 'changed blockerFingerprint or evidenceHash',
2874
+ 'AIQaBusinessAssertion or before/action/after blocker artifact'
2875
+ ]
2876
+ : []),
2877
+ ...(action === 'repair_infra_only'
2878
+ ? [
2879
+ 'infra or compile gate passes, or failureClass changes',
2880
+ 'new evidenceHash from fresh preflight/build artifact'
2881
+ ]
2882
+ : []),
2883
+ ...(action === 'repair_release_hotfix_first'
2884
+ ? [
2885
+ 'GitHub commit proof recorded before live backend hotfix',
2886
+ 'release gate or hotfix durability evidence refreshed'
2887
+ ]
2888
+ : [])
2889
+ ])).slice(0, 20);
2890
+ const blocksProductRepair = input.blocksProductRepair === true
2891
+ || action === 'collect_new_evidence'
2892
+ || action === 'run_diagnosis_gate'
2893
+ || action === 'ask_customer_clarification'
2894
+ || action === 'repair_infra_only'
2895
+ || action === 'revise_diagnosis_scope';
2896
+ const waitingForNewEvidence = evidenceFreshness?.mustCollectNewEvidence === true
2897
+ || action === 'collect_new_evidence';
2898
+ const proofRequired = requiredEvidence.length > 0 || requiredResetEvidence.length > 0;
2899
+ const status: ResolveIOSupportContinuationProofCheckpointStatus = waitingForNewEvidence
2900
+ ? 'waiting_for_new_evidence'
2901
+ : proofRequired
2902
+ ? 'waiting_for_proof'
2903
+ : 'waiting_for_state_transition';
2904
+ return {
2905
+ required: true,
2906
+ status,
2907
+ action,
2908
+ reason: cleanText(input.reason || evidenceFreshness?.reason || '', 1000),
2909
+ startingFailureClass: cleanText(evidenceFreshness?.failureClass, 120),
2910
+ startingBlockerFingerprint: cleanText(evidenceFreshness?.blockerFingerprint, 160),
2911
+ startingEvidenceHash: cleanText(evidenceFreshness?.evidenceHash, 160),
2912
+ requiredEvidence,
2913
+ requiredResetEvidence,
2914
+ successRequiresNewEvidence: waitingForNewEvidence || blocksProductRepair,
2915
+ blocksProductRepairUntilChangedEvidence: blocksProductRepair,
2916
+ nextAction: waitingForNewEvidence
2917
+ ? 'collect_required_reset_evidence'
2918
+ : action
2919
+ };
2920
+ }
2921
+
2922
+ export function changedFilesOutsideResolveIOSupportDiagnosisOwnerFiles(
2923
+ diagnosisGate: any,
2924
+ changedFiles: any,
2925
+ options: { allowTests?: boolean } = {}
2926
+ ): string[] {
2927
+ const validation = validateResolveIOSupportDiagnosisGate(diagnosisGate);
2928
+ if (!validation.valid || !validation.normalized) {
2929
+ return cleanList(changedFiles, 80, 500);
2930
+ }
2931
+ const owners = new Set(validation.normalized.owner_files.map(normalizeOwnerFilePath));
2932
+ return cleanList(changedFiles, 120, 500)
2933
+ .map(normalizeOwnerFilePath)
2934
+ .filter((filePath) => {
2935
+ if (!filePath) {
2936
+ return false;
2937
+ }
2938
+ if (owners.has(filePath)) {
2939
+ return false;
2940
+ }
2941
+ if (options.allowTests && /(^|\/)(tests?|spec|__tests__)(\/|$)|\.(?:spec|test)\.[jt]sx?$/i.test(filePath)) {
2942
+ return false;
2943
+ }
2944
+ return true;
2945
+ });
2946
+ }
2947
+
2948
+ export function decideResolveIOSupportV5RepairGate(input: ResolveIOSupportV5RepairGateInput): ResolveIOSupportV5RepairGateDecision {
2949
+ const activeStepType = cleanText(input.activeStepType, 80);
2950
+ const failureClass = cleanText(input.failureClass, 80).toLowerCase();
2951
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(input.diagnosisGate, {
2952
+ maxOwnerFiles: input.maxOwnerFiles
2953
+ });
2954
+ const ownerFiles = diagnosisValidation.normalized?.owner_files || [];
2955
+ const outsideOwnerFiles = changedFilesOutsideResolveIOSupportDiagnosisOwnerFiles(
2956
+ diagnosisValidation.normalized || input.diagnosisGate,
2957
+ input.changedFiles,
2958
+ { allowTests: input.allowTestsOutsideOwnerFiles === true }
2959
+ );
2960
+ const repeatedFailure = input.history?.length ? decideResolveIOSupportV5RepeatedFailureStop({
2961
+ history: input.history,
2962
+ failureClass,
2963
+ blocker: input.blocker,
2964
+ evidence: input.evidence,
2965
+ evidenceHash: input.evidenceHash,
2966
+ changedFiles: input.changedFiles,
2967
+ artifactPaths: input.artifactPaths,
2968
+ limit: Math.max(1, Number(input.maxRepeatedNoProgress || 2) || 2),
2969
+ ignoreInfra: true
2970
+ }) : undefined;
2971
+ const recoveryPlanFor = (
2972
+ action: string,
2973
+ reason: string,
2974
+ recoveryFailureClass = failureClass || 'unknown',
2975
+ productRepairFailure = false
2976
+ ): ResolveIOAIManagerRecoveryPlan => buildResolveIOAIManagerRecoveryPlan({
2977
+ action,
2978
+ reason,
2979
+ failureClass: recoveryFailureClass,
2980
+ lane: 'build',
2981
+ stepType: activeStepType || 'build_repair',
2982
+ blocker: input.blocker || (diagnosisValidation.blockers || []).join(' | '),
2983
+ changedFiles: cleanList(input.changedFiles, 40, 500),
2984
+ maxSameFailureRepeats: Math.max(1, Number(input.maxRepeatedNoProgress || 2) || 2),
2985
+ productRepairFailure
2986
+ });
2987
+ const recoveryCheckpointFor = (
2988
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan
2989
+ ): ResolveIOAIManagerRecoveryCheckpoint => buildResolveIOAIManagerRecoveryCheckpoint({
2990
+ plan: recoveryPlan,
2991
+ current: {
2992
+ lane: 'build',
2993
+ stepType: activeStepType || 'build_repair',
2994
+ failureClass: failureClass || 'unknown',
2995
+ blocker: input.blocker || (diagnosisValidation.blockers || []).join(' | '),
2996
+ evidenceHash: input.evidenceHash,
2997
+ changedFiles: cleanList(input.changedFiles, 40, 500),
2998
+ summary: input.blocker
2999
+ }
3000
+ });
3001
+ const recoveryFieldsFor = (
3002
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan
3003
+ ): Pick<ResolveIOSupportV5RepairGateDecision, 'recoveryPlan' | 'recoveryCheckpoint' | 'recoveryEvidenceProbe' | 'recoveryAction'> => {
3004
+ const recoveryCheckpoint = recoveryCheckpointFor(recoveryPlan);
3005
+ const recoveryEvidenceProbe = buildResolveIOAIManagerRecoveryEvidenceProbe({
3006
+ checkpoint: recoveryCheckpoint,
3007
+ current: {
3008
+ lane: 'build',
3009
+ stepType: activeStepType || 'build_repair',
3010
+ failureClass: failureClass || 'unknown',
3011
+ blocker: input.blocker || (diagnosisValidation.blockers || []).join(' | '),
3012
+ evidenceHash: input.evidenceHash,
3013
+ changedFiles: cleanList(input.changedFiles, 40, 500),
3014
+ summary: input.blocker
3015
+ }
3016
+ });
3017
+ const current = {
3018
+ lane: 'build',
3019
+ stepType: activeStepType || 'build_repair',
3020
+ failureClass: failureClass || 'unknown',
3021
+ blocker: input.blocker || (diagnosisValidation.blockers || []).join(' | '),
3022
+ evidenceHash: input.evidenceHash,
3023
+ changedFiles: cleanList(input.changedFiles, 40, 500),
3024
+ summary: input.blocker
3025
+ };
3026
+ return {
3027
+ recoveryPlan,
3028
+ recoveryCheckpoint,
3029
+ recoveryEvidenceProbe,
3030
+ recoveryAction: buildResolveIOAIManagerRecoveryActionPacket({
3031
+ plan: recoveryPlan,
3032
+ checkpoint: recoveryCheckpoint,
3033
+ probe: recoveryEvidenceProbe,
3034
+ current
3035
+ })
3036
+ };
3037
+ };
3038
+ if (repeatedFailure?.shouldStop) {
3039
+ const recoveryPlan = recoveryPlanFor('park_repeated_failure', repeatedFailure.reason, repeatedFailure.failureClass, true);
3040
+ return {
3041
+ action: 'park_repeated_failure',
3042
+ canEditProductCode: false,
3043
+ blockers: [repeatedFailure.reason],
3044
+ ownerFiles,
3045
+ issueClass: diagnosisValidation.normalized?.issue_class,
3046
+ proofPlan: diagnosisValidation.normalized?.proof_plan,
3047
+ outsideOwnerFiles,
3048
+ repeatedFailure,
3049
+ diagnosisValidation,
3050
+ ...recoveryFieldsFor(recoveryPlan)
3051
+ };
3052
+ }
3053
+ if (failureClass === 'infra' || failureClass === 'compile') {
3054
+ const recoveryPlan = recoveryPlanFor('retry_infra', 'support_v5_infra_or_compile_repair_required', failureClass, false);
3055
+ return {
3056
+ action: 'infra_repair_only',
3057
+ canEditProductCode: false,
3058
+ blockers: ['Infra/compile/Puppeteer/startup failures must be repaired in the harness lane before product-code repair.'],
3059
+ ownerFiles,
3060
+ issueClass: diagnosisValidation.normalized?.issue_class,
3061
+ proofPlan: diagnosisValidation.normalized?.proof_plan,
3062
+ outsideOwnerFiles,
3063
+ repeatedFailure,
3064
+ diagnosisValidation,
3065
+ ...recoveryFieldsFor(recoveryPlan)
3066
+ };
3067
+ }
3068
+ if (activeStepType === 'diagnosis_gate' || !diagnosisValidation.valid) {
3069
+ const recoveryPlan = recoveryPlanFor('continue', 'support_v5_diagnosis_gate_required', 'diagnosis', false);
3070
+ return {
3071
+ action: 'diagnose_only',
3072
+ canEditProductCode: false,
3073
+ blockers: diagnosisValidation.blockers.length
3074
+ ? diagnosisValidation.blockers
3075
+ : ['SupportDiagnosisGate is required before product-code repair.'],
3076
+ ownerFiles,
3077
+ issueClass: diagnosisValidation.normalized?.issue_class,
3078
+ proofPlan: diagnosisValidation.normalized?.proof_plan,
3079
+ outsideOwnerFiles,
3080
+ repeatedFailure,
3081
+ diagnosisValidation,
3082
+ ...recoveryFieldsFor(recoveryPlan)
3083
+ };
3084
+ }
3085
+ if (outsideOwnerFiles.length) {
3086
+ const recoveryPlan = recoveryPlanFor('continue', 'support_v5_out_of_scope_requires_diagnosis_revision', 'owner_scope', false);
3087
+ return {
3088
+ action: 'reject_out_of_scope',
3089
+ canEditProductCode: false,
3090
+ blockers: [`Changed files outside diagnosis owner_files; revise diagnosis with new evidence before broadening edits: ${outsideOwnerFiles.join(', ')}`],
3091
+ ownerFiles,
3092
+ issueClass: diagnosisValidation.normalized?.issue_class,
3093
+ proofPlan: diagnosisValidation.normalized?.proof_plan,
3094
+ outsideOwnerFiles,
3095
+ repeatedFailure,
3096
+ diagnosisValidation,
3097
+ ...recoveryFieldsFor(recoveryPlan)
3098
+ };
3099
+ }
3100
+ const recoveryPlan = recoveryPlanFor('continue', 'support_v5_product_repair_allowed_after_diagnosis', failureClass || 'product_code', true);
3101
+ return {
3102
+ action: 'allow_product_repair',
3103
+ canEditProductCode: true,
3104
+ blockers: [],
3105
+ ownerFiles,
3106
+ issueClass: diagnosisValidation.normalized?.issue_class,
3107
+ proofPlan: diagnosisValidation.normalized?.proof_plan,
3108
+ outsideOwnerFiles,
3109
+ repeatedFailure,
3110
+ diagnosisValidation,
3111
+ ...recoveryFieldsFor(recoveryPlan)
3112
+ };
3113
+ }
3114
+
3115
+ export function applyResolveIOSupportDiagnosisGateToMicrotasks(
3116
+ bundle: ResolveIOSupportV5StateBundle,
3117
+ diagnosisGate: any
3118
+ ): ResolveIOSupportV5StateBundle {
3119
+ const validation = validateResolveIOSupportDiagnosisGate(diagnosisGate);
3120
+ const gate = validation.normalized;
3121
+ if (!gate) {
3122
+ return bundle;
3123
+ }
3124
+ const ownerFiles = gate.owner_files;
3125
+ const probes = buildResolveIOSupportIssueClassProbes(gate);
3126
+ const proofContract = gate.proof_plan.business_proof_contract;
3127
+ const businessProof = proofContract?.expected_business_state_change || gate.proof_plan.business_assertion;
3128
+ const businessAssertion = proofContract?.data_or_dom_assertion || gate.proof_plan.data_assertion || gate.proof_plan.after;
3129
+ const now = isoNow();
3130
+ const diagnosisMicrotaskId = (bundle.supportV5MicrotaskLedger || [])
3131
+ .find((task) => task.type === 'diagnosis_gate')?.microtaskId
3132
+ || stableIdFromText('diagnosis', `Root-cause-first diagnosis gate for: ${cleanText(bundle.supportV5ScopeDigest || gate.issue_case.customer_complaint, 360) || 'support ticket'}`);
3133
+ const ledger = (bundle.supportV5MicrotaskLedger || []).map((task) => {
3134
+ if (task.type === 'diagnosis_gate') {
3135
+ return {
3136
+ ...task,
3137
+ status: validation.valid ? 'pass' as ResolveIOSupportV5MicrotaskStatus : 'needs_repair' as ResolveIOSupportV5MicrotaskStatus,
3138
+ blocker: validation.valid ? '' : validation.blockers.join(' | '),
3139
+ updatedAt: now
3140
+ };
3141
+ }
3142
+ if (task.lane === 'build' && /repair|product_repair|build_repair/i.test(String(task.type || ''))) {
3143
+ return {
3144
+ ...task,
3145
+ targetFiles: ownerFiles,
3146
+ contextRefs: Array.from(new Set([...(task.contextRefs || []), 'supportV5DiagnosisGate', 'owner_files'])),
3147
+ selfGate: `Repair only the diagnosed owner files unless you revise the diagnosis with new evidence. Owner files: ${ownerFiles.join(', ')}.`,
3148
+ acceptanceProof: businessProof,
3149
+ dependsOn: Array.from(new Set([...(task.dependsOn || []), diagnosisMicrotaskId])),
3150
+ updatedAt: now
3151
+ };
3152
+ }
3153
+ if (task.lane === 'qa' && task.type === 'qa_row') {
3154
+ return {
3155
+ ...task,
3156
+ contextRefs: Array.from(new Set([...(task.contextRefs || []), 'supportV5DiagnosisGate', 'proof_plan'])),
3157
+ selfGate: probes[0]?.action || task.selfGate,
3158
+ acceptanceProof: businessProof || task.acceptanceProof,
3159
+ targetFiles: ownerFiles,
3160
+ updatedAt: now
3161
+ };
3162
+ }
3163
+ return task;
3164
+ });
3165
+ const nextActive = selectResolveIOSupportV5ActiveMicrotask(ledger, bundle.supportV5ActiveMicrotaskId);
3166
+ return {
3167
+ ...bundle,
3168
+ supportV5DiagnosisGate: gate,
3169
+ supportV5MicrotaskLedger: ledger,
3170
+ supportV5ActiveMicrotaskId: nextActive?.microtaskId,
3171
+ supportV5LaneMemory: {
3172
+ ...bundle.supportV5LaneMemory,
3173
+ build: {
3174
+ ...bundle.supportV5LaneMemory.build,
3175
+ changedFiles: ownerFiles,
3176
+ scopeSummary: [
3177
+ bundle.supportV5LaneMemory.build.scopeSummary,
3178
+ `Diagnosis issue class: ${gate.issue_class}`,
3179
+ `Accepted hypothesis: ${gate.accepted_hypothesis.statement}`,
3180
+ `Owner files: ${ownerFiles.join(', ')}`
3181
+ ].filter(Boolean).join(' | '),
3182
+ updatedAt: now
3183
+ },
3184
+ qa: {
3185
+ ...bundle.supportV5LaneMemory.qa,
3186
+ activeQaRow: {
3187
+ workflow: proofContract?.action_under_test || gate.proof_plan.action,
3188
+ route: gate.proof_plan.route || gate.issue_case.route_module,
3189
+ assertion: businessAssertion,
3190
+ status: 'pending'
3191
+ },
3192
+ updatedAt: now
3193
+ }
3194
+ }
3195
+ };
3196
+ }
3197
+
3198
+ function stableIdFromText(prefix: string, value: string): string {
3199
+ const text = cleanText(value, 2000).toLowerCase();
3200
+ let hash = 0;
3201
+ for (let index = 0; index < text.length; index += 1) {
3202
+ hash = ((hash << 5) - hash + text.charCodeAt(index)) | 0;
3203
+ }
3204
+ return `${prefix}-${Math.abs(hash).toString(36) || '0'}`;
3205
+ }
3206
+
3207
+ function estimateTextTokens(value: string): number {
3208
+ const text = String(value || '');
3209
+ if (!text) {
3210
+ return 0;
3211
+ }
3212
+ return Math.max(1, Math.ceil(text.length / 4));
3213
+ }
3214
+
3215
+ export function fingerprintResolveIOSupportV5Blocker(value: any): string {
3216
+ const text = cleanText(value, 4000)
3217
+ .toLowerCase()
3218
+ .replace(/[a-f0-9]{16,}/g, '<id>')
3219
+ .replace(/\b\d{2,}\b/g, '<n>')
3220
+ .replace(/\bline\s+<n>\b/g, 'line <n>');
3221
+ let hash = 0;
3222
+ for (let index = 0; index < text.length; index += 1) {
3223
+ hash = ((hash << 5) - hash + text.charCodeAt(index)) | 0;
3224
+ }
3225
+ return `v5-${Math.abs(hash).toString(36)}`;
3226
+ }
3227
+
3228
+ export function buildResolveIOSupportV5Budget(existing?: Partial<ResolveIOSupportV5Budget>): ResolveIOSupportV5Budget {
3229
+ return {
3230
+ maxPromptTokensPerNonInitialStep: Number(existing?.maxPromptTokensPerNonInitialStep || 1800),
3231
+ maxLoopsPerTicket: Number(existing?.maxLoopsPerTicket || 24),
3232
+ maxRepeatedNoProgress: Number(existing?.maxRepeatedNoProgress || 2),
3233
+ maxRuntimeMinutesPerLoop: Number(existing?.maxRuntimeMinutesPerLoop || 15),
3234
+ totalPromptTokenEstimate: Number(existing?.totalPromptTokenEstimate || 0),
3235
+ totalRuntimeMs: Number(existing?.totalRuntimeMs || 0),
3236
+ loopCount: Number(existing?.loopCount || 0)
3237
+ };
3238
+ }
3239
+
3240
+ export function buildResolveIOSupportV5PromptBudget(existing?: Partial<ResolveIOSupportV5PromptBudget>): ResolveIOSupportV5PromptBudget {
3241
+ return {
3242
+ initialPlannerCap: Number(existing?.initialPlannerCap || 6000),
3243
+ buildMicrotaskCap: Number(existing?.buildMicrotaskCap || 1800),
3244
+ buildMicrotaskHardCap: Number(existing?.buildMicrotaskHardCap || 2500),
3245
+ qaMicrotaskCap: Number(existing?.qaMicrotaskCap || 1500),
3246
+ qaMicrotaskHardCap: Number(existing?.qaMicrotaskHardCap || 2200),
3247
+ repairMicrotaskCap: Number(existing?.repairMicrotaskCap || 1200),
3248
+ repairMicrotaskHardCap: Number(existing?.repairMicrotaskHardCap || 1800)
3249
+ };
3250
+ }
3251
+
3252
+ export function buildResolveIOSupportV5ScopeDigest(input: {
3253
+ goal?: string;
3254
+ approvedScope?: string | string[];
3255
+ prBranch?: string;
3256
+ maxTokens?: number;
3257
+ }): string {
3258
+ const scope = Array.isArray(input.approvedScope)
3259
+ ? cleanList(input.approvedScope, 30, 220).join(' | ')
3260
+ : cleanText(input.approvedScope, 4000);
3261
+ const raw = [
3262
+ input.goal ? `Goal: ${cleanText(input.goal, 300)}` : '',
3263
+ scope ? `Approved scope: ${scope}` : '',
3264
+ input.prBranch ? `PR branch: ${cleanText(input.prBranch, 160)}` : ''
3265
+ ].filter(Boolean).join('\n');
3266
+ const maxChars = Math.max(400, Math.floor(Number(input.maxTokens || 1000) * 4));
3267
+ return raw.slice(0, maxChars);
3268
+ }
3269
+
3270
+ export function buildResolveIOSupportV5MicrotaskLedger(input: {
3271
+ scopeDigest: string;
3272
+ requirements?: string[];
3273
+ buildThreadKey: string;
3274
+ qaThreadKey: string;
3275
+ now?: Date | string;
3276
+ existing?: ResolveIOSupportV5Microtask[];
3277
+ }): ResolveIOSupportV5Microtask[] {
3278
+ const existing = Array.isArray(input.existing) ? input.existing : [];
3279
+ const completedByObjective = new Map<string, ResolveIOSupportV5Microtask>();
3280
+ for (const task of existing) {
3281
+ if (task && (task.status === 'pass' || task.status === 'parked')) {
3282
+ completedByObjective.set(cleanText(task.objective, 500).toLowerCase(), task);
3283
+ }
3284
+ }
3285
+ const now = isoNow(input.now);
3286
+ const requirements = cleanList(input.requirements, 30, 240);
3287
+ const sourceRequirements = requirements.length
3288
+ ? requirements
3289
+ : cleanText(input.scopeDigest, 1000).split(/\s+\|\s+|\r?\n/g).map((line) => line.trim()).filter(Boolean).slice(0, 12);
3290
+ const ledger: ResolveIOSupportV5Microtask[] = [];
3291
+ const diagnosisObjective = `Root-cause-first diagnosis gate for: ${cleanText(input.scopeDigest, 360) || 'support ticket'}`;
3292
+ const diagnosisId = stableIdFromText('diagnosis', diagnosisObjective);
3293
+ const existingDiagnosis = existing.find((task) => task?.type === 'diagnosis_gate' || task?.microtaskId === diagnosisId);
3294
+ ledger.push(existingDiagnosis && (existingDiagnosis.status === 'pass' || existingDiagnosis.status === 'parked') ? existingDiagnosis : {
3295
+ microtaskId: diagnosisId,
3296
+ lane: 'build',
3297
+ type: 'diagnosis_gate',
3298
+ status: existingDiagnosis?.status || 'pending',
3299
+ objective: diagnosisObjective,
3300
+ targetFiles: [],
3301
+ contextRefs: ['scope_digest', 'support_context', 'similar_tickets', 'similar_commits'],
3302
+ selfGate: 'Read-only diagnosis only: reproduce or explicitly classify the issue, accept one falsifiable root-cause hypothesis, reject alternatives, identify the failing path, cap owner_files, and define before/action/after business proof.',
3303
+ acceptanceProof: 'Valid ResolveIOSupportDiagnosisGate JSON with issue_case, issue_class, accepted_hypothesis, rejected_alternatives, failing_path, owner_files, proof_plan, evidence, and status=passed.',
3304
+ threadKey: input.buildThreadKey,
3305
+ promptTokenEstimate: existingDiagnosis?.promptTokenEstimate,
3306
+ attempts: existingDiagnosis?.attempts || 0,
3307
+ dependsOn: [],
3308
+ parentScopeId: stableIdFromText('scope', diagnosisObjective),
3309
+ blocker: existingDiagnosis?.blocker,
3310
+ createdAt: existingDiagnosis?.createdAt || now,
3311
+ updatedAt: existingDiagnosis?.updatedAt || now
3312
+ });
3313
+ sourceRequirements.forEach((requirement, index) => {
3314
+ const objective = cleanText(requirement, 240);
3315
+ if (!objective) {
3316
+ return;
3317
+ }
3318
+ const existingCompleted = completedByObjective.get(objective.toLowerCase());
3319
+ const buildId = stableIdFromText(`build-${index + 1}`, objective);
3320
+ const qaId = stableIdFromText(`qa-${index + 1}`, objective);
3321
+ ledger.push(existingCompleted && existingCompleted.lane === 'build' ? existingCompleted : {
3322
+ microtaskId: buildId,
3323
+ lane: 'build',
3324
+ type: 'build_repair',
3325
+ status: 'pending',
3326
+ objective,
3327
+ targetFiles: [],
3328
+ contextRefs: ['scope_digest'],
3329
+ selfGate: 'Run the smallest compile/type/unit check that proves this one behavior.',
3330
+ acceptanceProof: 'Concrete code/data proof for this behavior, with changed files listed.',
3331
+ threadKey: input.buildThreadKey,
3332
+ attempts: 0,
3333
+ dependsOn: [diagnosisId],
3334
+ parentScopeId: stableIdFromText('scope', objective),
3335
+ createdAt: now,
3336
+ updatedAt: now
3337
+ });
3338
+ ledger.push({
3339
+ microtaskId: qaId,
3340
+ lane: 'qa',
3341
+ type: 'qa_row',
3342
+ status: 'pending',
3343
+ objective: `QA proof for: ${objective}`,
3344
+ targetFiles: [],
3345
+ contextRefs: ['scope_digest', buildId],
3346
+ selfGate: 'Drive this one customer-facing workflow row in browser/localhost and capture one captioned proof artifact.',
3347
+ acceptanceProof: 'QA matrix row pass with route/data assertion, screenshot/caption artifact, and persisted before/after row/count/value proof for data-changing workflows.',
3348
+ threadKey: input.qaThreadKey,
3349
+ attempts: 0,
3350
+ dependsOn: [diagnosisId, buildId],
3351
+ parentScopeId: stableIdFromText('scope', objective),
3352
+ createdAt: now,
3353
+ updatedAt: now
3354
+ });
3355
+ });
3356
+ return ledger.length ? ledger : existing.slice(-80);
3357
+ }
3358
+
3359
+ export function selectResolveIOSupportV5ActiveMicrotask(
3360
+ ledger: ResolveIOSupportV5Microtask[] = [],
3361
+ preferredId?: string
3362
+ ): ResolveIOSupportV5Microtask | undefined {
3363
+ const byPreferred = preferredId ? ledger.find((task) => task.microtaskId === preferredId && !['pass', 'parked'].includes(task.status)) : undefined;
3364
+ if (byPreferred) {
3365
+ return byPreferred;
3366
+ }
3367
+ return ledger.find((task) => task.status === 'needs_repair')
3368
+ || ledger.find((task) => task.status === 'in_progress')
3369
+ || ledger.find((task) => task.type === 'diagnosis_gate' && task.status === 'pending')
3370
+ || ledger.find((task) => task.lane === 'build' && /repair/i.test(String(task.type || '')) && task.status === 'pending')
3371
+ || ledger.find((task) => task.status === 'pending')
3372
+ || ledger.find((task) => task.status === 'blocked');
3373
+ }
3374
+
3375
+ export function initializeResolveIOSupportV5State(input: ResolveIOSupportV5InitializeInput): ResolveIOSupportV5StateBundle {
3376
+ const now = isoNow(input.now);
3377
+ const existing = input.existing || {};
3378
+ const existingSupervisor = existing.supportV5SupervisorState;
3379
+ const existingLaneMemory = existing.supportV5LaneMemory || {} as ResolveIOSupportV5StateBundle['supportV5LaneMemory'];
3380
+ const scope = cleanList(input.approvedScopeRequirements, 24, 240).join(' | ')
3381
+ || cleanText(input.description, 1000)
3382
+ || cleanText(input.title, 300);
3383
+ const ticketLabel = cleanText(input.ticketNumber || input.ticketId || input.jobId, 120);
3384
+ const buildThreadKey = cleanText(input.buildThreadKey || existingLaneMemory.build?.threadKey || `support:${input.ticketId || input.jobId}:job:${input.jobId}:build`, 240);
3385
+ const qaThreadKey = cleanText(input.qaThreadKey || existingLaneMemory.qa?.threadKey || `support:${input.ticketId || input.jobId}:job:${input.jobId}:qa`, 240);
3386
+ const budget = buildResolveIOSupportV5Budget(existing.supportV5Budget);
3387
+ const existingDiagnosisGate = normalizeResolveIOSupportDiagnosisGate((existing as any).supportV5DiagnosisGate);
3388
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(existingDiagnosisGate);
3389
+ const scopeDigest = cleanText((existing as any).supportV5ScopeDigest, 4000) || buildResolveIOSupportV5ScopeDigest({
3390
+ goal: existingSupervisor?.currentGoal || `Resolve support ticket ${ticketLabel}`,
3391
+ approvedScope: scope,
3392
+ prBranch: input.prBranch || existingSupervisor?.prBranch || ''
3393
+ });
3394
+ const ledger = buildResolveIOSupportV5MicrotaskLedger({
3395
+ scopeDigest,
3396
+ requirements: cleanList(input.approvedScopeRequirements, 30, 240),
3397
+ buildThreadKey,
3398
+ qaThreadKey,
3399
+ now,
3400
+ existing: (existing as any).supportV5MicrotaskLedger
3401
+ });
3402
+ const activeMicrotask = selectResolveIOSupportV5ActiveMicrotask(ledger, (existing as any).supportV5ActiveMicrotaskId);
3403
+ const initialized: ResolveIOSupportV5StateBundle = {
3404
+ supportWorkflowVersion: 'v5',
3405
+ supportV5SupervisorState: {
3406
+ version: 'v5',
3407
+ status: existingSupervisor?.status || 'active',
3408
+ currentGoal: existingSupervisor?.currentGoal || `Resolve support ticket ${ticketLabel}`,
3409
+ approvedScope: existingSupervisor?.approvedScope || scope,
3410
+ prBranch: cleanText(input.prBranch || existingSupervisor?.prBranch || '', 240),
3411
+ activeStep: existingSupervisor?.activeStep || (diagnosisValidation.valid ? 'compile_check' : 'diagnosis_gate'),
3412
+ activeBlocker: existingSupervisor?.activeBlocker || (diagnosisValidation.valid ? '' : 'SupportDiagnosisGate required before product-code repair.'),
3413
+ lastGoodCheckpoint: existingSupervisor?.lastGoodCheckpoint || 'v5_initialized',
3414
+ currentQaRow: existingSupervisor?.currentQaRow,
3415
+ processLease: input.processLease || existingSupervisor?.processLease,
3416
+ artifactLinks: cleanList(existingSupervisor?.artifactLinks, 40, 500),
3417
+ noEmailUnlessApproved: true,
3418
+ updatedAt: now
3419
+ },
3420
+ supportV5DiagnosisGate: diagnosisValidation.valid ? diagnosisValidation.normalized : existingDiagnosisGate,
3421
+ supportV5DiagnosisEvidencePack: (existing as any).supportV5DiagnosisEvidencePack || (existing as any).support_v5_diagnosis_evidence_pack,
3422
+ supportV5LaneMemory: {
3423
+ build: {
3424
+ lane: 'build',
3425
+ model: existingLaneMemory.build?.model || 'gpt-5.3-codex',
3426
+ threadKey: buildThreadKey,
3427
+ scopeSummary: existingLaneMemory.build?.scopeSummary || scope,
3428
+ activeBlocker: existingLaneMemory.build?.activeBlocker || '',
3429
+ activeQaRow: existingLaneMemory.build?.activeQaRow,
3430
+ changedFiles: cleanList(existingLaneMemory.build?.changedFiles, 80, 500),
3431
+ artifactPaths: cleanList(existingLaneMemory.build?.artifactPaths, 80, 500),
3432
+ latestPromptTokenEstimate: Number(existingLaneMemory.build?.latestPromptTokenEstimate || 0) || undefined,
3433
+ updatedAt: existingLaneMemory.build?.updatedAt || now
3434
+ },
3435
+ qa: {
3436
+ lane: 'qa',
3437
+ model: existingLaneMemory.qa?.model || 'gpt-5.4-mini',
3438
+ threadKey: qaThreadKey,
3439
+ scopeSummary: existingLaneMemory.qa?.scopeSummary || scope,
3440
+ activeBlocker: existingLaneMemory.qa?.activeBlocker || '',
3441
+ activeQaRow: existingLaneMemory.qa?.activeQaRow,
3442
+ changedFiles: cleanList(existingLaneMemory.qa?.changedFiles, 80, 500),
3443
+ artifactPaths: cleanList(existingLaneMemory.qa?.artifactPaths, 80, 500),
3444
+ latestPromptTokenEstimate: Number(existingLaneMemory.qa?.latestPromptTokenEstimate || 0) || undefined,
3445
+ updatedAt: existingLaneMemory.qa?.updatedAt || now
3446
+ }
3447
+ },
3448
+ supportV5StepHistory: Array.isArray(existing.supportV5StepHistory)
3449
+ ? existing.supportV5StepHistory.slice(-80)
3450
+ : [],
3451
+ supportV5Budget: budget,
3452
+ supportV5RunnerIncidents: Array.isArray(existing.supportV5RunnerIncidents)
3453
+ ? existing.supportV5RunnerIncidents.slice(-80)
3454
+ : [],
3455
+ supportV5MicrotaskLedger: ledger,
3456
+ supportV5ActiveMicrotaskId: diagnosisValidation.valid
3457
+ ? activeMicrotask?.microtaskId
3458
+ : ledger.find((task) => task.type === 'diagnosis_gate' && !['pass', 'parked'].includes(task.status))?.microtaskId || activeMicrotask?.microtaskId,
3459
+ supportV5ScopeDigest: scopeDigest,
3460
+ supportV5MicrotaskUsageHistory: Array.isArray((existing as any).supportV5MicrotaskUsageHistory)
3461
+ ? (existing as any).supportV5MicrotaskUsageHistory.slice(-200)
3462
+ : [],
3463
+ supportV5FailureFingerprints: Array.isArray((existing as any).supportV5FailureFingerprints)
3464
+ ? (existing as any).supportV5FailureFingerprints.slice(-200)
3465
+ : [],
3466
+ supportV5RecoveryPlan: (existing as any).supportV5RecoveryPlan,
3467
+ supportV5RecoveryCheckpoint: (existing as any).supportV5RecoveryCheckpoint,
3468
+ supportV5RecoveryEvidenceProbe: (existing as any).supportV5RecoveryEvidenceProbe,
3469
+ supportV5RecoveryAction: (existing as any).supportV5RecoveryAction,
3470
+ supportV5RecoveryDispatchHistory: Array.isArray((existing as any).supportV5RecoveryDispatchHistory)
3471
+ ? (existing as any).supportV5RecoveryDispatchHistory.slice(-50)
3472
+ : [],
3473
+ supportV5RecoveryDirective: (existing as any).supportV5RecoveryDirective
3474
+ };
3475
+ return diagnosisValidation.valid && diagnosisValidation.normalized
3476
+ ? applyResolveIOSupportDiagnosisGateToMicrotasks(initialized, diagnosisValidation.normalized)
3477
+ : initialized;
3478
+ }
3479
+
3480
+ export function recordResolveIOSupportV5Step(
3481
+ bundle: ResolveIOSupportV5StateBundle,
3482
+ step: ResolveIOSupportV5StepInput
3483
+ ): ResolveIOSupportV5StateBundle {
3484
+ const now = isoNow(step.now);
3485
+ const promptTokens = Math.max(0, Number(step.promptTokenEstimate || 0) || 0);
3486
+ const runtimeMs = Math.max(0, Number(step.runtimeMs || 0) || 0);
3487
+ const microtaskId = cleanText(step.microtaskId || bundle.supportV5ActiveMicrotaskId, 160);
3488
+ const normalizedDiagnosisGate = step.diagnosisGate
3489
+ ? normalizeResolveIOSupportDiagnosisGate(step.diagnosisGate, now)
3490
+ : undefined;
3491
+ const blockerFingerprint = step.blocker || step.summary
3492
+ ? fingerprintResolveIOSupportV5Blocker(step.blocker || step.summary || '')
3493
+ : undefined;
3494
+ const evidenceHash = cleanText(step.evidenceHash, 80)
3495
+ || (step.artifactPaths?.length || step.blocker || step.summary
3496
+ ? hashResolveIOSupportV5Evidence(step.artifactPaths?.length ? step.artifactPaths : `${step.blocker || ''}\n${step.summary || ''}`)
3497
+ : hashResolveIOAIManagerEvidence({
3498
+ failureClass: step.failureClass,
3499
+ blocker: step.blocker,
3500
+ summary: step.summary,
3501
+ changedFiles: step.changedFiles,
3502
+ artifactPaths: step.artifactPaths
3503
+ }));
3504
+ const record: ResolveIOSupportV5StepRecord = {
3505
+ ...(microtaskId ? { microtaskId } : {}),
3506
+ stepType: step.stepType,
3507
+ outcome: step.outcome,
3508
+ lane: step.lane,
3509
+ model: cleanText(step.model, 80),
3510
+ threadKey: cleanText(step.threadKey, 240),
3511
+ promptTokenEstimate: promptTokens || undefined,
3512
+ runtimeMs: runtimeMs || undefined,
3513
+ summary: cleanText(step.summary || step.blocker || step.outcome, 1200),
3514
+ blocker: cleanText(step.blocker, 1200),
3515
+ changedFiles: cleanList(step.changedFiles, 80, 500),
3516
+ artifactPaths: cleanList(step.artifactPaths, 80, 500),
3517
+ diagnosisGate: normalizedDiagnosisGate,
3518
+ failureClass: cleanText(step.failureClass, 80) || undefined,
3519
+ blockerFingerprint,
3520
+ evidenceHash,
3521
+ recordedAt: now
3522
+ };
3523
+ const diagnosisGate = normalizedDiagnosisGate || bundle.supportV5DiagnosisGate;
3524
+ const laneMemory = { ...bundle.supportV5LaneMemory };
3525
+ if (step.lane === 'build' || step.lane === 'qa') {
3526
+ const previous = laneMemory[step.lane];
3527
+ laneMemory[step.lane] = {
3528
+ ...previous,
3529
+ activeBlocker: record.blocker || previous.activeBlocker || '',
3530
+ activeQaRow: step.activeQaRow || previous.activeQaRow,
3531
+ changedFiles: record.changedFiles?.length ? record.changedFiles : previous.changedFiles,
3532
+ artifactPaths: record.artifactPaths?.length ? record.artifactPaths : previous.artifactPaths,
3533
+ latestPromptTokenEstimate: promptTokens || previous.latestPromptTokenEstimate,
3534
+ updatedAt: now
3535
+ };
3536
+ }
3537
+ const supervisor = {
3538
+ ...bundle.supportV5SupervisorState,
3539
+ status: step.outcome === 'ready_for_merge' ? 'complete' as const : (step.outcome === 'park_manual' || step.outcome === 'budget_stop' ? 'parked' as const : 'active' as const),
3540
+ activeStep: step.stepType,
3541
+ activeBlocker: record.blocker || '',
3542
+ currentQaRow: step.activeQaRow || bundle.supportV5SupervisorState.currentQaRow,
3543
+ lastGoodCheckpoint: step.outcome === 'pass' || step.outcome === 'ready_for_merge'
3544
+ ? step.stepType
3545
+ : bundle.supportV5SupervisorState.lastGoodCheckpoint,
3546
+ artifactLinks: Array.from(new Set([
3547
+ ...bundle.supportV5SupervisorState.artifactLinks,
3548
+ ...(record.artifactPaths || [])
3549
+ ])).slice(-80),
3550
+ updatedAt: now
3551
+ };
3552
+ const ledger = (bundle.supportV5MicrotaskLedger || []).map((task) => {
3553
+ if (task.microtaskId !== bundle.supportV5ActiveMicrotaskId) {
3554
+ return task;
3555
+ }
3556
+ const status: ResolveIOSupportV5MicrotaskStatus = step.outcome === 'pass' || step.outcome === 'ready_for_merge'
3557
+ ? 'pass'
3558
+ : step.outcome === 'park_manual' || step.outcome === 'budget_stop'
3559
+ ? 'parked'
3560
+ : step.outcome === 'needs_repair' || step.outcome === 'retry_same_step'
3561
+ ? 'needs_repair'
3562
+ : 'in_progress';
3563
+ return {
3564
+ ...task,
3565
+ status,
3566
+ blocker: record.blocker || task.blocker,
3567
+ promptTokenEstimate: promptTokens || task.promptTokenEstimate,
3568
+ attempts: task.attempts + (step.outcome === 'pass' || step.outcome === 'ready_for_merge' ? 0 : 1),
3569
+ updatedAt: now
3570
+ };
3571
+ });
3572
+ const failureFingerprint = record.failureClass && record.blockerFingerprint && record.evidenceHash
3573
+ ? {
3574
+ stepType: record.stepType,
3575
+ failureClass: cleanText(record.failureClass, 80),
3576
+ blockerFingerprint: record.blockerFingerprint,
3577
+ evidenceHash: record.evidenceHash,
3578
+ recordedAt: now
3579
+ }
3580
+ : undefined;
3581
+ const managerDecision = decideResolveIOAIManagerPolicy({
3582
+ history: [...bundle.supportV5StepHistory, record],
3583
+ current: record,
3584
+ maxSameFailureRepeats: buildResolveIOSupportV5Budget(bundle.supportV5Budget).maxRepeatedNoProgress,
3585
+ maxPingPongTransitions: 3,
3586
+ infraFailureClasses: ['infra', 'compile']
3587
+ });
3588
+ const previousRecord = bundle.supportV5StepHistory[bundle.supportV5StepHistory.length - 1];
3589
+ const nextLoopCount = managerDecision.loopBudgetShouldReset
3590
+ || previousRecord?.failureClass !== record.failureClass
3591
+ || previousRecord?.blockerFingerprint !== record.blockerFingerprint
3592
+ || previousRecord?.evidenceHash !== record.evidenceHash
3593
+ ? 1
3594
+ : bundle.supportV5Budget.loopCount + 1;
3595
+ const nextMicrotask = selectResolveIOSupportV5ActiveMicrotask(ledger, bundle.supportV5ActiveMicrotaskId);
3596
+ const nextBundle: ResolveIOSupportV5StateBundle = {
3597
+ ...bundle,
3598
+ supportV5SupervisorState: supervisor,
3599
+ supportV5DiagnosisGate: diagnosisGate,
3600
+ supportV5LaneMemory: laneMemory,
3601
+ supportV5StepHistory: [...bundle.supportV5StepHistory, record].slice(-100),
3602
+ supportV5Budget: {
3603
+ ...bundle.supportV5Budget,
3604
+ totalPromptTokenEstimate: bundle.supportV5Budget.totalPromptTokenEstimate + promptTokens,
3605
+ totalRuntimeMs: bundle.supportV5Budget.totalRuntimeMs + runtimeMs,
3606
+ loopCount: nextLoopCount
3607
+ },
3608
+ supportV5MicrotaskLedger: ledger,
3609
+ supportV5ActiveMicrotaskId: nextMicrotask?.microtaskId,
3610
+ supportV5MicrotaskUsageHistory: bundle.supportV5MicrotaskUsageHistory || [],
3611
+ supportV5FailureFingerprints: failureFingerprint
3612
+ ? [...(bundle.supportV5FailureFingerprints || []), failureFingerprint].slice(-200)
3613
+ : (bundle.supportV5FailureFingerprints || []),
3614
+ supportV5RecoveryPlan: managerDecision.recoveryPlan,
3615
+ supportV5RecoveryCheckpoint: managerDecision.recoveryCheckpoint,
3616
+ supportV5RecoveryEvidenceProbe: managerDecision.recoveryEvidenceProbe,
3617
+ supportV5RecoveryAction: managerDecision.recoveryAction
3618
+ };
3619
+ if (normalizedDiagnosisGate && validateResolveIOSupportDiagnosisGate(normalizedDiagnosisGate).valid) {
3620
+ return applyResolveIOSupportDiagnosisGateToMicrotasks(nextBundle, normalizedDiagnosisGate);
3621
+ }
3622
+ return nextBundle;
3623
+ }
3624
+
3625
+ export function recordResolveIOSupportV5MicrotaskUsage(
3626
+ bundle: ResolveIOSupportV5StateBundle,
3627
+ usage: Omit<ResolveIOSupportV5MicrotaskUsage, 'recordedAt'>
3628
+ ): ResolveIOSupportV5StateBundle {
3629
+ const record: ResolveIOSupportV5MicrotaskUsage = {
3630
+ ...usage,
3631
+ microtaskId: cleanText(usage.microtaskId, 160),
3632
+ threadKey: cleanText(usage.threadKey, 240),
3633
+ model: cleanText(usage.model, 80),
3634
+ promptTokenEstimate: Math.max(0, Number(usage.promptTokenEstimate || 0) || 0),
3635
+ promptSections: Array.isArray(usage.promptSections)
3636
+ ? usage.promptSections.map((section) => ({
3637
+ name: cleanText(section.name, 120),
3638
+ tokenEstimate: Math.max(0, Number(section.tokenEstimate || 0) || 0)
3639
+ })).filter((section) => section.name)
3640
+ : [],
3641
+ recordedAt: isoNow()
3642
+ };
3643
+ const ledger = (bundle.supportV5MicrotaskLedger || []).map((task) => task.microtaskId === record.microtaskId
3644
+ ? {
3645
+ ...task,
3646
+ promptTokenEstimate: record.promptTokenEstimate,
3647
+ updatedAt: record.recordedAt
3648
+ }
3649
+ : task);
3650
+ return {
3651
+ ...bundle,
3652
+ supportV5MicrotaskLedger: ledger,
3653
+ supportV5MicrotaskUsageHistory: [
3654
+ ...(bundle.supportV5MicrotaskUsageHistory || []),
3655
+ record
3656
+ ].slice(-200)
3657
+ };
3658
+ }
3659
+
3660
+ export function decideResolveIOSupportV5Continuation(bundle: ResolveIOSupportV5StateBundle): ResolveIOSupportV5ContinuationDecision {
3661
+ const history = bundle.supportV5StepHistory || [];
3662
+ const budget = buildResolveIOSupportV5Budget(bundle.supportV5Budget);
3663
+ const last = history[history.length - 1];
3664
+ const activeMicrotaskId = cleanText(bundle.supportV5ActiveMicrotaskId, 160);
3665
+ const blockerFingerprint = fingerprintResolveIOSupportV5Blocker(last?.blocker || last?.summary || '');
3666
+ let repeatedNoProgressCount = 0;
3667
+ for (let index = history.length - 1; index >= 0; index -= 1) {
3668
+ const item = history[index];
3669
+ if (activeMicrotaskId && cleanText((item as any).microtaskId, 160) !== activeMicrotaskId) {
3670
+ continue;
3671
+ }
3672
+ if (item.outcome === 'pass' || item.outcome === 'ready_for_merge') {
3673
+ break;
3674
+ }
3675
+ if (item.stepType !== last?.stepType) {
3676
+ break;
3677
+ }
3678
+ if (fingerprintResolveIOSupportV5Blocker(item.blocker || item.summary || '') === blockerFingerprint) {
3679
+ repeatedNoProgressCount += 1;
3680
+ }
3681
+ }
3682
+ const budgetExceeded = budget.loopCount >= budget.maxLoopsPerTicket
3683
+ || (last?.promptTokenEstimate || 0) > budget.maxPromptTokensPerNonInitialStep;
3684
+ const repeatedFailure = last ? decideResolveIOSupportV5RepeatedFailureStop({
3685
+ history,
3686
+ failureClass: last.failureClass,
3687
+ blocker: last.blocker || last.summary,
3688
+ evidenceHash: last.evidenceHash,
3689
+ changedFiles: last.changedFiles,
3690
+ artifactPaths: last.artifactPaths,
3691
+ limit: budget.maxRepeatedNoProgress,
3692
+ ignoreInfra: true
3693
+ }) : null;
3694
+ const recoveryPlanFor = (
3695
+ action: string,
3696
+ reason: string,
3697
+ extra: Partial<ResolveIOAIManagerRecoveryPlanInput> = {}
3698
+ ): ResolveIOAIManagerRecoveryPlan => buildResolveIOAIManagerRecoveryPlan({
3699
+ action,
3700
+ reason,
3701
+ failureClass: last?.failureClass,
3702
+ lane: last?.lane || 'supervisor',
3703
+ stepType: last?.stepType || bundle.supportV5SupervisorState.activeStep,
3704
+ blocker: last?.blocker || last?.summary,
3705
+ changedFiles: last?.changedFiles,
3706
+ artifactPaths: last?.artifactPaths,
3707
+ maxSameFailureRepeats: budget.maxRepeatedNoProgress,
3708
+ ...extra
3709
+ });
3710
+ const recoveryCheckpointFor = (
3711
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan
3712
+ ): ResolveIOAIManagerRecoveryCheckpoint => buildResolveIOAIManagerRecoveryCheckpoint({
3713
+ plan: recoveryPlan,
3714
+ current: last
3715
+ });
3716
+ const recoveryFieldsFor = (
3717
+ recoveryPlan: ResolveIOAIManagerRecoveryPlan
3718
+ ): Pick<ResolveIOSupportV5ContinuationDecision, 'recoveryPlan' | 'recoveryCheckpoint' | 'recoveryEvidenceProbe' | 'recoveryAction'> => {
3719
+ const recoveryCheckpoint = recoveryCheckpointFor(recoveryPlan);
3720
+ const recoveryEvidenceProbe = buildResolveIOAIManagerRecoveryEvidenceProbe({
3721
+ checkpoint: recoveryCheckpoint,
3722
+ current: last
3723
+ });
3724
+ return {
3725
+ recoveryPlan,
3726
+ recoveryCheckpoint,
3727
+ recoveryEvidenceProbe,
3728
+ recoveryAction: buildResolveIOAIManagerRecoveryActionPacket({
3729
+ plan: recoveryPlan,
3730
+ checkpoint: recoveryCheckpoint,
3731
+ probe: recoveryEvidenceProbe,
3732
+ current: last
3733
+ })
3734
+ };
3735
+ };
3736
+ if (budgetExceeded) {
3737
+ const recoveryPlan = recoveryPlanFor('manual_handoff', 'support_v5_budget_guard', {
3738
+ productRepairFailure: false
3739
+ });
3740
+ return {
3741
+ action: 'park',
3742
+ reason: 'support_v5_budget_guard',
3743
+ nextStep: last?.stepType || 'cleanup',
3744
+ repeatedNoProgressCount,
3745
+ budgetExceeded,
3746
+ ...recoveryFieldsFor(recoveryPlan)
3747
+ };
3748
+ }
3749
+ const lastFailureClass = cleanText(last?.failureClass, 80).toLowerCase();
3750
+ const materialEvidenceRetryAllowed = repeatedFailure?.shouldStop === false
3751
+ && repeatedFailure.newEvidence === true
3752
+ && repeatedFailure.materialEvidence === true;
3753
+ if (repeatedNoProgressCount > budget.maxRepeatedNoProgress && /^(infra|compile)$/.test(lastFailureClass)) {
3754
+ const recoveryPlan = recoveryPlanFor('retry_infra', 'support_v5_infra_or_compile_repair_required', {
3755
+ failureClass: lastFailureClass,
3756
+ productRepairFailure: false
3757
+ });
3758
+ return {
3759
+ action: 'continue',
3760
+ reason: 'support_v5_infra_or_compile_repair_required',
3761
+ nextStep: last?.stepType || 'compile_check',
3762
+ repeatedNoProgressCount,
3763
+ budgetExceeded,
3764
+ ...recoveryFieldsFor(recoveryPlan)
3765
+ };
3766
+ }
3767
+ if (repeatedFailure?.shouldStop) {
3768
+ const recoveryPlan = recoveryPlanFor(
3769
+ repeatedFailure.reason === 'support_v5_ping_pong_failure_loop' ? 'park_ping_pong' : 'park_repeated_failure',
3770
+ repeatedFailure.reason,
3771
+ {
3772
+ failureClass: repeatedFailure.failureClass,
3773
+ productRepairFailure: true
3774
+ }
3775
+ );
3776
+ return {
3777
+ action: 'park',
3778
+ reason: repeatedFailure.reason,
3779
+ nextStep: last?.stepType || 'cleanup',
3780
+ repeatedNoProgressCount: repeatedFailure.repeatedCount,
3781
+ budgetExceeded,
3782
+ ...recoveryFieldsFor(recoveryPlan)
3783
+ };
3784
+ }
3785
+ if (repeatedNoProgressCount > budget.maxRepeatedNoProgress && !materialEvidenceRetryAllowed) {
3786
+ const recoveryPlan = recoveryPlanFor('park_repeated_failure', 'support_v5_repeated_no_progress', {
3787
+ productRepairFailure: true
3788
+ });
3789
+ return {
3790
+ action: 'park',
3791
+ reason: 'support_v5_repeated_no_progress',
3792
+ nextStep: last?.stepType || 'cleanup',
3793
+ repeatedNoProgressCount,
3794
+ budgetExceeded,
3795
+ ...recoveryFieldsFor(recoveryPlan)
3796
+ };
3797
+ }
3798
+ const recoveryPlan = recoveryPlanFor('continue', 'support_v5_continue');
3799
+ return {
3800
+ action: 'continue',
3801
+ reason: 'support_v5_continue',
3802
+ nextStep: last?.stepType || bundle.supportV5SupervisorState.activeStep,
3803
+ repeatedNoProgressCount,
3804
+ budgetExceeded: false,
3805
+ ...recoveryFieldsFor(recoveryPlan)
3806
+ };
3807
+ }
3808
+
3809
+ export function decideResolveIOSupportV5AutonomousNextAction(
3810
+ input: ResolveIOSupportV5AutonomousDecisionInput
3811
+ ): ResolveIOSupportV5AutonomousDecision {
3812
+ const bundle = input.bundle;
3813
+ const activeMicrotask = selectResolveIOSupportV5ActiveMicrotask(
3814
+ bundle.supportV5MicrotaskLedger || [],
3815
+ bundle.supportV5ActiveMicrotaskId
3816
+ );
3817
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(bundle.supportV5DiagnosisGate, {
3818
+ maxOwnerFiles: input.maxOwnerFiles
3819
+ });
3820
+ const activeStepType = cleanText(
3821
+ activeMicrotask?.type || (!diagnosisValidation.valid ? bundle.supportV5SupervisorState.activeStep : 'cleanup') || 'diagnosis_gate',
3822
+ 80
3823
+ ) as ResolveIOSupportV5StepType;
3824
+ const repairGate = decideResolveIOSupportV5RepairGate({
3825
+ diagnosisGate: bundle.supportV5DiagnosisGate,
3826
+ activeStepType,
3827
+ changedFiles: input.changedFiles,
3828
+ failureClass: input.failureClass,
3829
+ blocker: input.blocker,
3830
+ evidence: input.evidence,
3831
+ evidenceHash: input.evidenceHash,
3832
+ history: bundle.supportV5StepHistory,
3833
+ artifactPaths: input.artifactPaths,
3834
+ maxOwnerFiles: input.maxOwnerFiles,
3835
+ allowTestsOutsideOwnerFiles: true
3836
+ });
3837
+ const continuation = decideResolveIOSupportV5Continuation(bundle);
3838
+ const customerReplyPolicy = decideResolveIOSupportCustomerReplyPolicy({
3839
+ diagnosisGate: bundle.supportV5DiagnosisGate,
3840
+ outcomeLabel: input.outcomeLabel,
3841
+ confidence: input.confidence,
3842
+ businessAssertionStatus: input.businessAssertionStatus,
3843
+ businessAssertions: input.businessAssertions,
3844
+ businessProofArtifacts: input.businessProofArtifacts,
3845
+ previousProofFingerprint: input.previousProofFingerprint,
3846
+ previousArtifactFingerprint: input.previousArtifactFingerprint,
3847
+ unresolvedBlockers: input.unresolvedBlockers,
3848
+ releaseStatus: input.releaseStatus
3849
+ });
3850
+ const businessProofReadiness = evaluateResolveIOSupportBusinessProofReadiness({
3851
+ diagnosisGate: bundle.supportV5DiagnosisGate,
3852
+ outcomeLabel: input.outcomeLabel,
3853
+ businessAssertionStatus: input.businessAssertionStatus,
3854
+ businessAssertions: input.businessAssertions,
3855
+ businessProofArtifacts: input.businessProofArtifacts,
3856
+ previousProofFingerprint: input.previousProofFingerprint,
3857
+ previousArtifactFingerprint: input.previousArtifactFingerprint
3858
+ });
3859
+ const evidenceFreshness = evaluateResolveIOSupportEvidenceFreshness({
3860
+ history: bundle.supportV5StepHistory,
3861
+ failureClass: input.failureClass,
3862
+ blocker: input.blocker,
3863
+ evidence: input.evidence,
3864
+ evidenceHash: input.evidenceHash,
3865
+ changedFiles: input.changedFiles,
3866
+ artifactPaths: input.artifactPaths || input.businessProofArtifacts,
3867
+ lane: activeMicrotask?.lane || 'supervisor',
3868
+ stepType: activeStepType,
3869
+ limit: buildResolveIOSupportV5Budget(bundle.supportV5Budget).maxRepeatedNoProgress,
3870
+ ignoreInfra: true
3871
+ });
3872
+ const ownerFiles = diagnosisValidation.normalized?.owner_files || repairGate.ownerFiles || [];
3873
+ const proofContract = diagnosisValidation.normalized?.proof_plan.business_proof_contract;
3874
+ const expectedProof = proofContract?.data_or_dom_assertion
3875
+ || diagnosisValidation.normalized?.proof_plan.business_assertion
3876
+ || activeMicrotask?.acceptanceProof
3877
+ || '';
3878
+ const issueClassProbes = diagnosisValidation.valid && diagnosisValidation.normalized
3879
+ ? buildResolveIOSupportIssueClassProbes(diagnosisValidation.normalized)
3880
+ : [];
3881
+ const activeIssueClassProbe = issueClassProbes[0];
3882
+ const issueClassProbeEvidence = activeIssueClassProbe
3883
+ ? Array.from(new Set([
3884
+ `AIQaBusinessAssertion mapped to ${activeIssueClassProbe.issue_class}`,
3885
+ `state transition: ${activeIssueClassProbe.state_transition.before || 'before'} -> ${activeIssueClassProbe.state_transition.action || activeIssueClassProbe.action} -> ${activeIssueClassProbe.state_transition.after || 'after'}`,
3886
+ ...activeIssueClassProbe.required_artifacts.map((artifact) => `artifact: ${artifact}`)
3887
+ ]))
3888
+ : [];
3889
+ const buildRootCauseReadiness = (
3890
+ action: ResolveIOSupportV5AutonomousNextAction,
3891
+ reason: string,
3892
+ fields: Partial<ResolveIOSupportV5AutonomousDecision>,
3893
+ primaryCommand: string,
3894
+ nextCommands: string[],
3895
+ blockers: string[]
3896
+ ): ResolveIOSupportRootCauseReadiness => {
3897
+ const statusByAction: Record<ResolveIOSupportV5AutonomousNextAction, ResolveIOSupportRootCauseReadinessStatus> = {
3898
+ run_diagnosis_gate: 'diagnosis_required',
3899
+ ask_customer_clarification: 'customer_clarification_required',
3900
+ repair_infra_only: 'infra_repair_only',
3901
+ revise_diagnosis_scope: 'scope_revision_required',
3902
+ run_owner_scoped_repair: 'owner_scoped_repair_ready',
3903
+ run_business_proof_qa: 'business_proof_required',
3904
+ repair_release_hotfix_first: 'release_hotfix_required',
3905
+ collect_new_evidence: 'collect_new_evidence',
3906
+ draft_customer_reply: 'customer_reply_draft_ready',
3907
+ ready_for_release_gate: 'release_gate_ready',
3908
+ park_manual: 'parked'
3909
+ };
3910
+ const nextGateByStatus: Record<ResolveIOSupportRootCauseReadinessStatus, ResolveIOSupportRootCauseReadiness['nextGate']> = {
3911
+ diagnosis_required: 'diagnosis',
3912
+ customer_clarification_required: 'customer_reply',
3913
+ infra_repair_only: 'infra',
3914
+ scope_revision_required: 'scope',
3915
+ owner_scoped_repair_ready: 'repair',
3916
+ business_proof_required: 'business_proof',
3917
+ release_hotfix_required: 'release',
3918
+ release_gate_ready: 'release',
3919
+ customer_reply_draft_ready: 'customer_reply',
3920
+ collect_new_evidence: 'evidence',
3921
+ parked: 'manual'
3922
+ };
3923
+ const status = statusByAction[action] || 'parked';
3924
+ const diagnosisValid = diagnosisValidation.valid === true;
3925
+ const ownerFilesReady = diagnosisValid && ownerFiles.length > 0;
3926
+ const proofPlanReady = diagnosisValid && !!proofContract;
3927
+ const rootCauseFirstSatisfied = diagnosisValid && ownerFilesReady && proofPlanReady;
3928
+ const sameFailureParked = evidenceFreshness.mustCollectNewEvidence === true
3929
+ || (action === 'collect_new_evidence' && /repeated|no_progress|ping_pong|same failure|same evidence/i.test(reason));
3930
+ return {
3931
+ status,
3932
+ nextGate: nextGateByStatus[status],
3933
+ nextCommand: primaryCommand || nextCommands[0] || action,
3934
+ rootCauseFirstSatisfied,
3935
+ diagnosisValid,
3936
+ ownerFilesReady,
3937
+ proofPlanReady,
3938
+ businessProofReady: businessProofReadiness.ready === true,
3939
+ infraOnly: action === 'repair_infra_only',
3940
+ sameFailureParked,
3941
+ canEditProductCode: fields.canEditProductCode === true && rootCauseFirstSatisfied,
3942
+ canRunIssueClassProbe: rootCauseFirstSatisfied && (action === 'run_business_proof_qa' || status === 'business_proof_required'),
3943
+ canRunBusinessProofQa: rootCauseFirstSatisfied && (action === 'run_business_proof_qa' || !businessProofReadiness.ready),
3944
+ canRelease: action === 'ready_for_release_gate' && businessProofReadiness.ready === true,
3945
+ canDraftCustomerReply: (action === 'draft_customer_reply' && businessProofReadiness.ready === true)
3946
+ || action === 'ask_customer_clarification',
3947
+ requiresHumanDecision: action === 'park_manual' || fields.canRunAutonomously !== true,
3948
+ reason,
3949
+ blockers,
3950
+ ownerFiles,
3951
+ issueClass: diagnosisValidation.normalized?.issue_class || repairGate.issueClass,
3952
+ expectedProof,
3953
+ issueClassProbes,
3954
+ businessProofStatus: businessProofReadiness.status,
3955
+ proofFingerprint: businessProofReadiness.proofFingerprint,
3956
+ artifactFingerprint: businessProofReadiness.artifactFingerprint,
3957
+ proofFreshness: businessProofReadiness.proofFreshness
3958
+ };
3959
+ };
3960
+ const nextActionExpectedTransition = (
3961
+ action: ResolveIOSupportV5AutonomousNextAction,
3962
+ primaryCommand: string
3963
+ ): string => {
3964
+ if (action === 'run_diagnosis_gate') {
3965
+ return 'SupportDiagnosisGate changes from missing/incomplete to passed, blocked with one clarification question, or rejected with explicit blockers; no source files are edited.';
3966
+ }
3967
+ if (action === 'ask_customer_clarification') {
3968
+ return 'One customer clarification question is prepared for human review and the ticket parks until the missing reproduction/account context is supplied.';
3969
+ }
3970
+ if (action === 'repair_infra_only') {
3971
+ return 'Infra/preflight evidence changes or passes without charging the failure as product-code repair.';
3972
+ }
3973
+ if (action === 'revise_diagnosis_scope') {
3974
+ return 'SupportDiagnosisGate owner_files, failing_path, and proof_plan are revised together with new evidence before any broader edit.';
3975
+ }
3976
+ if (action === 'run_owner_scoped_repair') {
3977
+ return 'Only diagnosis owner_files change, a compile/unit gate is recorded, and the next state is business proof QA rather than acceptance.';
3978
+ }
3979
+ if (action === 'run_business_proof_qa') {
3980
+ return 'A fresh AIQaBusinessAssertion maps to the active proof_plan and records before/action/after DOM, data, or Mongo proof.';
3981
+ }
3982
+ if (action === 'repair_release_hotfix_first') {
3983
+ return primaryCommand === 'record_hotfix_evidence'
3984
+ ? 'Hotfix evidence is recorded with sourceCommitSha, githubCommitUrl, and passed gitPushStatus before any live backend apply or continuation.'
3985
+ : 'The smallest release/hotfix gate changes state, with duplicate full deploy blocked unless force evidence is explicit.';
3986
+ }
3987
+ if (action === 'ready_for_release_gate') {
3988
+ return 'Release gate records compile, business proof, and deployment readiness without treating route-only evidence as acceptance.';
3989
+ }
3990
+ if (action === 'draft_customer_reply') {
3991
+ return 'A customer resolution draft is prepared from accepted business proof and remains unsent until human approval.';
3992
+ }
3993
+ if (action === 'collect_new_evidence') {
3994
+ return 'The next run records a changed blocker fingerprint, changed evidence hash, fresh artifact path, or explicit business/infra/compile/release proof.';
3995
+ }
3996
+ return 'The runner stays parked until a human changes scope, budget, evidence, or autonomy policy.';
3997
+ };
3998
+ const buildNextActionContract = (
3999
+ action: ResolveIOSupportV5AutonomousNextAction,
4000
+ label: string,
4001
+ reason: string,
4002
+ fields: Partial<ResolveIOSupportV5AutonomousDecision>,
4003
+ rootCauseReadiness: ResolveIOSupportRootCauseReadiness,
4004
+ continuationProofCheckpoint: ResolveIOSupportContinuationProofCheckpoint,
4005
+ primaryCommand: string,
4006
+ nextCommands: string[],
4007
+ requiredEvidence: string[],
4008
+ forbiddenActions: string[],
4009
+ blockers: string[]
4010
+ ): ResolveIOSupportNextActionContract => {
4011
+ const lane = (fields.lane || activeMicrotask?.lane || 'supervisor') as ResolveIOSupportV5Lane | 'release' | 'customer';
4012
+ const stepType = (fields.stepType || activeStepType) as ResolveIOSupportV5StepType | 'release_gate' | 'customer_reply';
4013
+ const liveHotfixBlockedUntilCommit = fields.liveHotfixBlockedUntilCommit === true;
4014
+ const hotfixCommitRequired = action === 'repair_release_hotfix_first'
4015
+ || liveHotfixBlockedUntilCommit
4016
+ || fields.canHotfixBackend === true;
4017
+ const requiresHumanApproval = action === 'park_manual'
4018
+ || action === 'ask_customer_clarification'
4019
+ || rootCauseReadiness.requiresHumanDecision === true;
4020
+ const safeToAutoRun = fields.canRunAutonomously === true
4021
+ && !requiresHumanApproval
4022
+ && (action !== 'run_owner_scoped_repair' || (rootCauseReadiness.rootCauseFirstSatisfied === true && evidenceFreshness.mustCollectNewEvidence !== true))
4023
+ && (action !== 'repair_release_hotfix_first' || primaryCommand !== 'apply_backend_hotfix_only_after_commit_proof' || liveHotfixBlockedUntilCommit !== true);
4024
+ const costRisk: ResolveIOSupportNextActionCostRisk = requiresHumanApproval
4025
+ ? 'manual_blocked'
4026
+ : (fields.canHotfixBackend === true || action === 'draft_customer_reply')
4027
+ ? 'release_or_customer_send'
4028
+ : fields.canRunModel === true
4029
+ ? 'expensive_model'
4030
+ : fields.canRunQa === true
4031
+ ? 'small_model_or_qa'
4032
+ : 'free_or_deterministic';
4033
+ const canRunWithoutCodexMonitor = safeToAutoRun
4034
+ && continuationProofCheckpoint.required === true
4035
+ && forbiddenActions.length > 0
4036
+ && (requiredEvidence.length > 0 || action === 'repair_infra_only' || action === 'run_diagnosis_gate');
4037
+ const codexFallbackRequired = !canRunWithoutCodexMonitor && !requiresHumanApproval;
4038
+ const codexFallbackReason = codexFallbackRequired
4039
+ ? (blockers[0] || continuationProofCheckpoint.reason || 'next_action_contract_missing_required_evidence')
4040
+ : requiresHumanApproval
4041
+ ? 'human_decision_required_not_codex_fallback'
4042
+ : !canRunWithoutCodexMonitor
4043
+ ? 'structured_contract_blocks_auto_run_without_requesting_codex_monitor'
4044
+ : 'structured_next_action_contract_allows_manager_execution_without_external_codex_monitor';
4045
+ const preconditions = Array.from(new Set([
4046
+ action !== 'run_diagnosis_gate' ? 'SupportDiagnosisGate validation checked before action.' : 'Diagnosis is read-only and cannot edit source files.',
4047
+ action === 'run_owner_scoped_repair' ? 'Root-cause-first gate is satisfied and owner_files are the only editable product files.' : '',
4048
+ action === 'run_business_proof_qa' ? 'Business proof QA must use the diagnosis proof_plan and generated issue-class probe.' : '',
4049
+ action === 'repair_release_hotfix_first' ? 'Live backend hotfix is blocked until GitHub commit proof is recorded and passed.' : '',
4050
+ evidenceFreshness.mustCollectNewEvidence === true ? 'Current failure is stale or repeated; only evidence collection is allowed until proof changes.' : '',
4051
+ ...requiredEvidence
4052
+ ].filter(Boolean))).slice(0, 24);
4053
+ const stopConditions = Array.from(new Set([
4054
+ 'Stop if the same failure class, blocker fingerprint, and evidence hash repeat without material evidence.',
4055
+ 'Stop if the action would edit outside diagnosis owner_files without a revised diagnosis gate.',
4056
+ 'Stop if route-load, screenshot-only, scorecard-only, or model-claim proof is the only acceptance evidence.',
4057
+ liveHotfixBlockedUntilCommit ? 'Stop before live backend hotfix until sourceCommitSha, githubCommitUrl, and passed gitPushStatus are recorded.' : '',
4058
+ ...continuationProofCheckpoint.requiredResetEvidence.map((entry) => `Reset requires: ${entry}`)
4059
+ ].filter(Boolean))).slice(0, 24);
4060
+ const createdAt = isoNow(input.now);
4061
+ return {
4062
+ contractId: `support-next-action-${hashResolveIOSupportV5Evidence({
4063
+ action,
4064
+ primaryCommand,
4065
+ reason,
4066
+ blockers,
4067
+ requiredEvidence,
4068
+ evidenceFreshness: evidenceFreshness.evidenceHash,
4069
+ blockerFingerprint: evidenceFreshness.blockerFingerprint,
4070
+ createdAt: createdAt.slice(0, 16)
4071
+ }).slice(0, 16)}`,
4072
+ action,
4073
+ label,
4074
+ primaryCommand,
4075
+ lane,
4076
+ stepType,
4077
+ safeToAutoRun,
4078
+ requiresHumanApproval,
4079
+ canRunWithoutCodexMonitor,
4080
+ codexFallbackRequired,
4081
+ codexFallbackReason,
4082
+ costRisk,
4083
+ rootCauseFirstSatisfied: rootCauseReadiness.rootCauseFirstSatisfied === true,
4084
+ decisionBasis: {
4085
+ diagnosisValid: rootCauseReadiness.diagnosisValid === true,
4086
+ ownerFilesReady: rootCauseReadiness.ownerFilesReady === true,
4087
+ proofPlanReady: rootCauseReadiness.proofPlanReady === true,
4088
+ businessProofReady: businessProofReadiness.ready === true,
4089
+ evidenceFreshnessStatus: evidenceFreshness.status,
4090
+ evidenceStrength: evidenceFreshness.evidenceStrength,
4091
+ failureClass: evidenceFreshness.failureClass,
4092
+ blockerFingerprint: evidenceFreshness.blockerFingerprint,
4093
+ evidenceHash: evidenceFreshness.evidenceHash,
4094
+ sameFailureCount: evidenceFreshness.sameFailureCount,
4095
+ hotfixCommitRequired,
4096
+ liveHotfixBlockedUntilCommit
4097
+ },
4098
+ preconditions,
4099
+ expectedStateTransition: nextActionExpectedTransition(action, primaryCommand),
4100
+ successEvidence: Array.from(new Set(requiredEvidence.length ? requiredEvidence : continuationProofCheckpoint.requiredEvidence)).slice(0, 24),
4101
+ stopConditions,
4102
+ forbiddenActions: forbiddenActions.slice(0, 24),
4103
+ ownerFiles: ownerFiles.slice(0, 24),
4104
+ blockers: blockers.slice(0, 24),
4105
+ nextCommands: nextCommands.slice(0, 24),
4106
+ createdAt
4107
+ };
4108
+ };
4109
+ const makeDecision = (
4110
+ action: ResolveIOSupportV5AutonomousNextAction,
4111
+ label: string,
4112
+ reason: string,
4113
+ fields: Partial<ResolveIOSupportV5AutonomousDecision>
4114
+ ): ResolveIOSupportV5AutonomousDecision => {
4115
+ const primaryCommand = fields.primaryCommand || action;
4116
+ const nextCommands = fields.nextCommands || [primaryCommand];
4117
+ const requiredEvidence = fields.requiredEvidence || [];
4118
+ const blockers = fields.blockers || [];
4119
+ const rootCauseReadiness = buildRootCauseReadiness(action, reason, fields, primaryCommand, nextCommands, blockers);
4120
+ const forbiddenActions = Array.from(new Set([
4121
+ 'Do not send customer email without explicit human approval.',
4122
+ 'Do not broaden owner_files without revised diagnosis evidence.',
4123
+ 'Do not accept route-load, screenshot-only, scorecard-only, or model-claim proof.',
4124
+ ...(fields.forbiddenActions || [])
4125
+ ]));
4126
+ const continuationProofCheckpoint = buildResolveIOSupportContinuationProofCheckpoint({
4127
+ action,
4128
+ reason,
4129
+ evidenceFreshness,
4130
+ requiredEvidence,
4131
+ requiredResetEvidence: fields.requiredEvidence,
4132
+ blocksProductRepair: evidenceFreshness.mustCollectNewEvidence === true
4133
+ });
4134
+ const nextActionContract = buildNextActionContract(
4135
+ action,
4136
+ label,
4137
+ reason,
4138
+ fields,
4139
+ rootCauseReadiness,
4140
+ continuationProofCheckpoint,
4141
+ primaryCommand,
4142
+ nextCommands,
4143
+ requiredEvidence,
4144
+ forbiddenActions,
4145
+ blockers
4146
+ );
4147
+ const humanReviewPacket = fields.humanReviewPacket
4148
+ || (action === 'draft_customer_reply' && customerReplyPolicy.humanReviewPacket ? customerReplyPolicy.humanReviewPacket : undefined)
4149
+ || buildResolveIOSupportHumanReviewPacket({
4150
+ reviewType: supportAutonomousReviewTypeForAction(action),
4151
+ title: label,
4152
+ summary: reason,
4153
+ primaryAction: primaryCommand,
4154
+ customerFacingDraftAllowed: fields.canDraftCustomerReply === true,
4155
+ safety: action === 'draft_customer_reply' ? 'safe_to_draft' : 'internal_hold',
4156
+ reason,
4157
+ blockers,
4158
+ requiredEvidence,
4159
+ evidenceRefs: businessProofReadiness.artifactPaths,
4160
+ nextCommands,
4161
+ forbiddenActions,
4162
+ costRisk: fields.canHotfixBackend === true || action === 'draft_customer_reply'
4163
+ ? 'release_or_customer_send'
4164
+ : fields.canRunModel === true
4165
+ ? 'expensive_model'
4166
+ : fields.canRunQa === true
4167
+ ? 'small_model_or_qa'
4168
+ : 'free_or_deterministic',
4169
+ now: input.now
4170
+ });
4171
+ return {
4172
+ action,
4173
+ label,
4174
+ reason,
4175
+ canRunAutonomously: fields.canRunAutonomously === true,
4176
+ canEditProductCode: fields.canEditProductCode === true,
4177
+ canRunModel: fields.canRunModel === true,
4178
+ canRunQa: fields.canRunQa === true,
4179
+ canPrepareHotfixPatch: fields.canPrepareHotfixPatch === true,
4180
+ canHotfixBackend: fields.canHotfixBackend === true,
4181
+ liveHotfixBlockedUntilCommit: fields.liveHotfixBlockedUntilCommit === true,
4182
+ canDraftCustomerReply: fields.canDraftCustomerReply === true,
4183
+ canSendCustomerReply: false,
4184
+ lane: fields.lane || activeMicrotask?.lane || 'supervisor',
4185
+ stepType: fields.stepType || activeStepType,
4186
+ microtaskId: activeMicrotask?.microtaskId,
4187
+ primaryCommand,
4188
+ nextCommands,
4189
+ requiredEvidence,
4190
+ forbiddenActions,
4191
+ blockers,
4192
+ ownerFiles,
4193
+ issueClass: diagnosisValidation.normalized?.issue_class || repairGate.issueClass,
4194
+ expectedProof,
4195
+ issueClassProbes,
4196
+ activeMicrotask,
4197
+ diagnosisValidation,
4198
+ repairGate,
4199
+ continuation,
4200
+ customerReplyPolicy,
4201
+ businessProofReadiness,
4202
+ evidenceFreshness,
4203
+ rootCauseReadiness,
4204
+ continuationProofCheckpoint,
4205
+ nextActionContract,
4206
+ humanReviewPacket,
4207
+ hotfixContinuation: fields.hotfixContinuation,
4208
+ recordedAt: isoNow(input.now)
4209
+ };
4210
+ };
4211
+ if (continuation.action === 'park' && continuation.reason === 'support_v5_budget_guard') {
4212
+ return makeDecision('park_manual', 'Park Manual', continuation.reason, {
4213
+ canRunAutonomously: false,
4214
+ lane: 'supervisor',
4215
+ stepType: 'cleanup',
4216
+ primaryCommand: 'park_support_ticket_for_manual_budget_review',
4217
+ requiredEvidence: ['budget summary', 'latest blocker', 'operator decision'],
4218
+ blockers: ['Support V5 budget guard is active.']
4219
+ });
4220
+ }
4221
+ if (continuation.action === 'park') {
4222
+ return makeDecision('collect_new_evidence', 'Collect New Evidence', continuation.reason, {
4223
+ canRunAutonomously: true,
4224
+ canRunModel: false,
4225
+ canRunQa: true,
4226
+ lane: activeMicrotask?.lane || 'qa',
4227
+ stepType: activeStepType,
4228
+ primaryCommand: 'run_support_v5_recovery_evidence_probe',
4229
+ nextCommands: continuation.recoveryAction.nextCommands,
4230
+ requiredEvidence: continuation.recoveryAction.requiredArtifacts,
4231
+ blockers: [continuation.reason],
4232
+ forbiddenActions: ['Do not run another product-code repair until blockerFingerprint or evidenceHash changes.']
4233
+ });
4234
+ }
4235
+ if (customerReplyPolicy.action === 'ask_clarification') {
4236
+ return makeDecision('ask_customer_clarification', 'Ask Customer Clarification', customerReplyPolicy.reason, {
4237
+ canRunAutonomously: false,
4238
+ canRunModel: false,
4239
+ canEditProductCode: false,
4240
+ canDraftCustomerReply: true,
4241
+ lane: 'customer',
4242
+ stepType: 'customer_reply',
4243
+ primaryCommand: customerReplyPolicy.humanReviewPacket?.primaryAction || 'review_customer_clarification',
4244
+ nextCommands: customerReplyPolicy.humanReviewPacket?.nextCommands || ['edit_clarification_question', 'send_after_human_review', 'park_ticket_until_customer_reply'],
4245
+ requiredEvidence: customerReplyPolicy.requiredEvidence,
4246
+ blockers: diagnosisValidation.blockers,
4247
+ forbiddenActions: [
4248
+ 'Do not run repair from a guessed reproduction path.',
4249
+ 'Do not send customer email without explicit human approval.'
4250
+ ],
4251
+ humanReviewPacket: customerReplyPolicy.humanReviewPacket
4252
+ });
4253
+ }
4254
+ if (repairGate.action === 'diagnose_only' || activeMicrotask?.type === 'diagnosis_gate' || !diagnosisValidation.valid) {
4255
+ return makeDecision('run_diagnosis_gate', 'Run Diagnosis Gate', 'support_v5_root_cause_first_required', {
4256
+ canRunAutonomously: true,
4257
+ canRunModel: true,
4258
+ canEditProductCode: false,
4259
+ lane: 'build',
4260
+ stepType: 'diagnosis_gate',
4261
+ primaryCommand: 'run_support_v5_read_only_diagnosis_gate',
4262
+ nextCommands: ['retrieve_similar_tickets_and_commits', 'run_reproduction_or_classification_probe', 'write_support_diagnosis_gate_json'],
4263
+ requiredEvidence: [
4264
+ 'issue_case expected/observed/account context',
4265
+ 'accepted falsifiable hypothesis',
4266
+ 'rejected alternatives',
4267
+ 'failing path',
4268
+ 'small owner_files set',
4269
+ 'before/action/after business proof plan'
4270
+ ],
4271
+ blockers: repairGate.blockers,
4272
+ forbiddenActions: ['No source edits during diagnosis.']
4273
+ });
4274
+ }
4275
+ if (repairGate.action === 'infra_repair_only') {
4276
+ return makeDecision('repair_infra_only', 'Repair Infra Only', 'support_v5_infra_failure_before_product_repair', {
4277
+ canRunAutonomously: true,
4278
+ canRunModel: false,
4279
+ canEditProductCode: false,
4280
+ canRunQa: true,
4281
+ lane: 'qa',
4282
+ stepType: activeStepType,
4283
+ primaryCommand: 'run_support_v5_infra_repair',
4284
+ nextCommands: ['rerun_puppeteer_compile_startup_preflight', 'repair_harness_or_cache_only', 'record_infra_artifact'],
4285
+ requiredEvidence: ['preflight log', 'compile/startup/browser status', 'new infra blocker hash or pass'],
4286
+ blockers: repairGate.blockers,
4287
+ forbiddenActions: ['Do not charge infra failures as product-code repair failures.']
4288
+ });
4289
+ }
4290
+ if (repairGate.action === 'reject_out_of_scope') {
4291
+ return makeDecision('revise_diagnosis_scope', 'Revise Diagnosis Scope', 'support_v5_owner_scope_block', {
4292
+ canRunAutonomously: true,
4293
+ canRunModel: true,
4294
+ canEditProductCode: false,
4295
+ lane: 'build',
4296
+ stepType: 'diagnosis_gate',
4297
+ primaryCommand: 'revise_support_diagnosis_gate_with_new_owner_file_evidence',
4298
+ nextCommands: ['attach_out_of_scope_diff', 'prove_new_owner_file_is_required', 'update_support_diagnosis_gate'],
4299
+ requiredEvidence: ['new root-cause evidence for each added owner file'],
4300
+ blockers: repairGate.blockers,
4301
+ forbiddenActions: ['Do not edit files outside owner_files before diagnosis is revised.']
4302
+ });
4303
+ }
4304
+ if (repairGate.action === 'park_repeated_failure') {
4305
+ return makeDecision('collect_new_evidence', 'Collect New Evidence', 'support_v5_repeated_failure_needs_new_evidence', {
4306
+ canRunAutonomously: true,
4307
+ canRunModel: false,
4308
+ canRunQa: true,
4309
+ lane: activeMicrotask?.lane || 'qa',
4310
+ stepType: activeStepType,
4311
+ primaryCommand: 'run_support_v5_recovery_evidence_probe',
4312
+ nextCommands: repairGate.recoveryAction.nextCommands,
4313
+ requiredEvidence: repairGate.recoveryAction.requiredArtifacts,
4314
+ blockers: repairGate.blockers,
4315
+ forbiddenActions: ['Do not run another repair loop until new material evidence exists.']
4316
+ });
4317
+ }
4318
+ if (evidenceFreshness.mustCollectNewEvidence === true && repairGate.action === 'allow_product_repair') {
4319
+ return makeDecision('collect_new_evidence', 'Collect New Evidence', 'support_v5_no_blind_loop_requires_changed_evidence', {
4320
+ canRunAutonomously: true,
4321
+ canRunModel: false,
4322
+ canRunQa: true,
4323
+ canEditProductCode: false,
4324
+ lane: activeMicrotask?.lane || 'qa',
4325
+ stepType: activeStepType,
4326
+ primaryCommand: 'run_support_v5_recovery_evidence_probe',
4327
+ nextCommands: ['execute_issue_class_probe', 'record_changed_blocker_or_business_proof', 'write_aiqa_business_assertion_or_blocker_artifact'],
4328
+ requiredEvidence: evidenceFreshness.requiredResetEvidence.length
4329
+ ? evidenceFreshness.requiredResetEvidence
4330
+ : ['changed blockerFingerprint or evidenceHash', 'fresh issue-specific business proof artifact'],
4331
+ blockers: [evidenceFreshness.reason || 'Repeated failure needs fresh evidence before owner-scoped repair.'],
4332
+ forbiddenActions: ['Do not edit product code in this recovery; collect changed browser/data/business-proof evidence only.']
4333
+ });
4334
+ }
4335
+ if (supportReleaseLooksBlocked(input.releaseStatus)) {
4336
+ const releaseBlocker = cleanList(input.unresolvedBlockers, 20, 500).join('; ')
4337
+ || cleanText(input.blocker || input.releaseStatus, 1000)
4338
+ || 'Support release is blocked.';
4339
+ const hotfixContinuation = decideResolveIOAIManagerHotfixContinuation({
4340
+ evidence: input.hotfixEvidence,
4341
+ policy: input.releasePolicy,
4342
+ releaseGatePassed: input.releaseGatePassed,
4343
+ failureClass: 'release',
4344
+ blocker: releaseBlocker,
4345
+ now: input.now
4346
+ });
4347
+ const hotfixPrimaryCommandByAction: Record<string, string> = {
4348
+ record_hotfix_evidence: 'record_hotfix_evidence',
4349
+ request_force_deploy_reason: 'request_force_deploy_reason',
4350
+ rerun_release_gate: 'rerun_support_release_gate_once',
4351
+ continue_runner: 'continue_support_runner_after_committed_hotfix',
4352
+ allow_one_full_deploy: 'execute_one_full_deploy_with_force_evidence',
4353
+ park_manual: 'park_support_release_manual'
4354
+ };
4355
+ const hotfixNextCommands = Array.from(new Set([
4356
+ 'classify_release_blocker',
4357
+ 'prepare_hotfix_patch_without_live_apply',
4358
+ 'commit_and_push_hotfix_to_github',
4359
+ 'record_github_commit_for_hotfix',
4360
+ ...hotfixContinuation.nextCommands,
4361
+ 'apply_backend_hotfix_only_after_commit_proof',
4362
+ 'rerun_release_gate_once'
4363
+ ]));
4364
+ const hotfixRequiredEvidence = Array.from(new Set([
4365
+ 'hotfix evidence',
4366
+ 'full sourceCommitSha, githubCommitUrl, and passed gitPushStatus for the exact pushed GitHub commit',
4367
+ 'checksum before/after',
4368
+ 'health/self-test pass',
4369
+ 'release gate result',
4370
+ ...hotfixContinuation.requiredEvidence
4371
+ ]));
4372
+ const hotfixForbiddenActions = [
4373
+ 'Do not apply a live hotfix before the exact diff is committed and pushed to GitHub.',
4374
+ 'Do not mark a hotfix durable without sourceCommitSha, githubCommitUrl, and passed gitPushStatus.',
4375
+ 'Do not run a full deploy to clear a duplicate release loop unless force evidence explicitly allows one.'
4376
+ ];
4377
+ const hotfixGitGuard = hotfixContinuation.githubCommitGuard;
4378
+ const liveHotfixBlockedUntilCommit = hotfixContinuation.action === 'record_hotfix_evidence'
4379
+ || hotfixGitGuard?.managerMustCommitBeforeHotfix === true
4380
+ || (hotfixGitGuard?.required === true && hotfixGitGuard?.passed !== true);
4381
+ const canPrepareHotfixPatch = hotfixContinuation.action !== 'park_manual';
4382
+ return makeDecision('repair_release_hotfix_first', 'Hotfix Release', 'support_v5_release_blocked_hotfix_first', {
4383
+ canRunAutonomously: hotfixContinuation.action !== 'park_manual',
4384
+ canRunModel: false,
4385
+ canEditProductCode: false,
4386
+ canPrepareHotfixPatch,
4387
+ canHotfixBackend: canPrepareHotfixPatch && !liveHotfixBlockedUntilCommit,
4388
+ liveHotfixBlockedUntilCommit,
4389
+ lane: 'release',
4390
+ stepType: 'release_gate',
4391
+ primaryCommand: hotfixPrimaryCommandByAction[hotfixContinuation.action] || 'record_hotfix_evidence',
4392
+ nextCommands: hotfixNextCommands,
4393
+ requiredEvidence: hotfixRequiredEvidence,
4394
+ blockers: Array.from(new Set([
4395
+ ...cleanList(input.unresolvedBlockers, 20, 500),
4396
+ ...hotfixContinuation.blockers
4397
+ ])),
4398
+ forbiddenActions: hotfixForbiddenActions,
4399
+ hotfixContinuation
4400
+ });
4401
+ }
4402
+ if (activeMicrotask?.lane === 'qa' || /^(qa_row|qa_retest|business_proof|route_probe|issue_class_probe)$/.test(activeStepType)) {
4403
+ return makeDecision('run_business_proof_qa', 'Run Business Proof QA', 'support_v5_business_assertion_required', {
4404
+ canRunAutonomously: true,
4405
+ canRunQa: true,
4406
+ canRunModel: false,
4407
+ canEditProductCode: false,
4408
+ lane: 'qa',
4409
+ stepType: /^(qa_row|qa_retest|business_proof|route_probe|issue_class_probe)$/.test(activeStepType) ? activeStepType : 'qa_row',
4410
+ primaryCommand: 'run_support_v5_business_proof_qa_row',
4411
+ nextCommands: ['start_local_stack_if_needed', 'execute_issue_class_probe', 'record_before_action_after_artifacts', 'write_aiqa_business_assertion'],
4412
+ requiredEvidence: Array.from(new Set(['AIQaBusinessAssertion pass', 'DOM/data proof', 'artifact path', 'Mongo delta when data-changing', ...issueClassProbeEvidence])),
4413
+ forbiddenActions: ['Route probe pass alone remains route evidence only.']
4414
+ });
4415
+ }
4416
+ if (!activeMicrotask && customerReplyPolicy.action === 'draft_resolution_reply') {
4417
+ return makeDecision('draft_customer_reply', 'Draft Customer Reply', customerReplyPolicy.reason, {
4418
+ canRunAutonomously: true,
4419
+ canRunModel: true,
4420
+ canDraftCustomerReply: true,
4421
+ lane: 'customer',
4422
+ stepType: 'customer_reply',
4423
+ primaryCommand: 'draft_support_customer_resolution_reply',
4424
+ nextCommands: ['summarize_business_proof', 'draft_customer_reply_for_human_review'],
4425
+ requiredEvidence: customerReplyPolicy.requiredEvidence,
4426
+ forbiddenActions: ['Draft only; do not send.']
4427
+ });
4428
+ }
4429
+ if (!activeMicrotask && diagnosisValidation.valid && !businessProofReadiness.ready) {
4430
+ return makeDecision('run_business_proof_qa', 'Run Business Proof QA', businessProofReadiness.reason, {
4431
+ canRunAutonomously: true,
4432
+ canRunQa: true,
4433
+ canRunModel: false,
4434
+ canEditProductCode: false,
4435
+ lane: 'qa',
4436
+ stepType: 'business_proof',
4437
+ primaryCommand: 'run_support_v5_business_proof_qa_row',
4438
+ nextCommands: ['execute_issue_class_probe', 'record_before_action_after_artifacts', 'write_aiqa_business_assertion'],
4439
+ requiredEvidence: Array.from(new Set([...businessProofReadiness.requiredEvidence, ...issueClassProbeEvidence])),
4440
+ blockers: businessProofReadiness.blockers,
4441
+ forbiddenActions: ['Do not run release or customer reply until businessProofReadiness.ready=true.']
4442
+ });
4443
+ }
4444
+ if (!activeMicrotask && businessProofReadiness.ready) {
4445
+ return makeDecision('ready_for_release_gate', 'Run Release Gate', 'support_v5_business_proof_complete_release_gate_ready', {
4446
+ canRunAutonomously: true,
4447
+ canRunQa: true,
4448
+ lane: 'release',
4449
+ stepType: 'release_gate',
4450
+ primaryCommand: 'run_support_release_gate_once',
4451
+ nextCommands: ['verify_compile_and_business_proof_artifacts', 'run_release_gate_once', 'record_release_status'],
4452
+ requiredEvidence: ['compile pass', 'business assertion artifact', 'release status']
4453
+ });
4454
+ }
4455
+ return makeDecision('run_owner_scoped_repair', 'Run Owner-Scoped Repair', 'support_v5_product_repair_allowed_after_diagnosis', {
4456
+ canRunAutonomously: true,
4457
+ canRunModel: true,
4458
+ canEditProductCode: true,
4459
+ lane: activeMicrotask?.lane || 'build',
4460
+ stepType: activeStepType || 'build_repair',
4461
+ primaryCommand: 'run_support_v5_owner_scoped_repair',
4462
+ nextCommands: ['load_owner_files_only', 'apply_smallest_fix', 'run_smallest_compile_or_unit_gate', 'handoff_to_business_proof_qa'],
4463
+ requiredEvidence: ['changed files within owner_files', 'compile/unit proof', 'next QA row'],
4464
+ forbiddenActions: ['Do not edit outside owner_files unless diagnosis is revised with new evidence.']
4465
+ });
4466
+ }
4467
+
4468
+ export function buildResolveIOSupportV5DiagnoseFirstPrompt(lines: {
4469
+ goal?: string;
4470
+ approvedScope?: string[];
4471
+ activeStep?: ResolveIOSupportV5StepType;
4472
+ activeBlocker?: string;
4473
+ lane?: ResolveIOSupportV5Lane;
4474
+ laneSummary?: string;
4475
+ currentQaRow?: ResolveIOSupportV5LaneMemory['activeQaRow'];
4476
+ artifactPaths?: string[];
4477
+ changedFiles?: string[];
4478
+ }): string[] {
4479
+ return [
4480
+ 'Support Runner V5 contract: act like a guarded autonomous engineer, not a gate checklist.',
4481
+ 'Start with Diagnose First: observed evidence, likely cause, smallest next action, and expected proof.',
4482
+ 'Use compact lane memory and current artifacts before asking for broad ticket context.',
4483
+ 'Do not send customer email. Do not write production data. Do not spawn duplicate build, QA, server, client, Mongo, browser, or Codex processes.',
4484
+ 'If repairing, inspect logs/artifacts/source/diff first; make the smallest code or localhost-data change, then run the smallest finite self-gate.',
4485
+ 'For build repairs, prefer `npm run build-prod -- --watch=false`, a targeted `tsc --noEmit`, or a focused unit test. `npm run build-dev` is forbidden for support microtasks because it is slow/watch-oriented and can leave heavy esbuild work. If no finite gate exists, return a blocked self-gate with the exact reason instead of running build-dev.',
4486
+ 'If QA fails, retest the failed row before advancing; if the same blocker repeats without new proof, park with the exact blocker.',
4487
+ lines.goal ? `Goal: ${cleanText(lines.goal, 500)}` : '',
4488
+ cleanList(lines.approvedScope, 12, 240).length ? `Approved scope: ${cleanList(lines.approvedScope, 12, 240).join(' | ')}` : '',
4489
+ lines.activeStep ? `Active step: ${lines.activeStep}` : '',
4490
+ lines.lane ? `Lane: ${lines.lane}` : '',
4491
+ lines.laneSummary ? `Lane memory: ${cleanText(lines.laneSummary, 700)}` : '',
4492
+ lines.activeBlocker ? `Active blocker: ${cleanText(lines.activeBlocker, 800)}` : '',
4493
+ lines.currentQaRow?.workflow ? `Current QA row: ${cleanText(lines.currentQaRow.workflow, 300)}` : '',
4494
+ lines.currentQaRow?.route ? `Current QA route: ${cleanText(lines.currentQaRow.route, 300)}` : '',
4495
+ cleanList(lines.changedFiles, 12, 200).length ? `Changed files: ${cleanList(lines.changedFiles, 12, 200).join(', ')}` : '',
4496
+ cleanList(lines.artifactPaths, 12, 240).length ? `Artifacts: ${cleanList(lines.artifactPaths, 12, 240).join(', ')}` : ''
4497
+ ].filter(Boolean);
4498
+ }
4499
+
4500
+ export function buildResolveIOSupportV5MicrotaskPrompt(input: {
4501
+ bundle: ResolveIOSupportV5StateBundle;
4502
+ lane: 'build' | 'qa';
4503
+ model?: string;
4504
+ stage?: string;
4505
+ failureText?: string;
4506
+ changedFiles?: string[];
4507
+ artifactPaths?: string[];
4508
+ targetFiles?: string[];
4509
+ contextSnippets?: string[];
4510
+ similarCaseHints?: any;
4511
+ activeQaRow?: ResolveIOSupportV5LaneMemory['activeQaRow'];
4512
+ }): {
4513
+ prompt: string;
4514
+ promptTokenEstimate: number;
4515
+ promptSections: ResolveIOSupportV5UsageSection[];
4516
+ diagnosisEvidencePack: ResolveIOSupportDiagnosisEvidencePack;
4517
+ activeMicrotask?: ResolveIOSupportV5Microtask;
4518
+ withinCap: boolean;
4519
+ withinHardCap: boolean;
4520
+ cap: number;
4521
+ hardCap: number;
4522
+ reason?: string;
4523
+ } {
4524
+ const activeMicrotask = selectResolveIOSupportV5ActiveMicrotask(
4525
+ input.bundle.supportV5MicrotaskLedger || [],
4526
+ input.bundle.supportV5ActiveMicrotaskId
4527
+ );
4528
+ const diagnosisValidation = validateResolveIOSupportDiagnosisGate(input.bundle.supportV5DiagnosisGate);
4529
+ const diagnosisGate = diagnosisValidation.normalized || input.bundle.supportV5DiagnosisGate;
4530
+ const diagnosisActive = activeMicrotask?.type === 'diagnosis_gate';
4531
+ const budget = buildResolveIOSupportV5PromptBudget();
4532
+ const repairLike = Boolean(input.failureText || activeMicrotask?.status === 'needs_repair' || /repair/i.test(String(input.stage || activeMicrotask?.type || '')));
4533
+ const cap = repairLike
4534
+ ? budget.repairMicrotaskCap
4535
+ : input.lane === 'qa'
4536
+ ? budget.qaMicrotaskCap
4537
+ : budget.buildMicrotaskCap;
4538
+ const hardCap = repairLike
4539
+ ? budget.repairMicrotaskHardCap
4540
+ : input.lane === 'qa'
4541
+ ? budget.qaMicrotaskHardCap
4542
+ : budget.buildMicrotaskHardCap;
4543
+ const laneMemory = input.bundle.supportV5LaneMemory[input.lane];
4544
+ const taskThreadKey = activeMicrotask?.threadKey || laneMemory.threadKey;
4545
+ const changedFiles = cleanList(input.changedFiles?.length ? input.changedFiles : laneMemory.changedFiles, 8, 160);
4546
+ const artifactPaths = cleanList(input.artifactPaths?.length ? input.artifactPaths : laneMemory.artifactPaths, 8, 200);
4547
+ const targetFiles = cleanList(input.targetFiles?.length ? input.targetFiles : activeMicrotask?.targetFiles, 5, 160);
4548
+ const contextSnippets = cleanList(input.contextSnippets, 5, 360);
4549
+ const diagnosisEvidencePack = buildResolveIOSupportDiagnosisEvidencePack({
4550
+ bundle: input.bundle,
4551
+ similarCaseHints: input.similarCaseHints,
4552
+ issueClass: (diagnosisGate as ResolveIOSupportDiagnosisGate | undefined)?.issue_class,
4553
+ ownerFiles: (diagnosisGate as ResolveIOSupportDiagnosisGate | undefined)?.owner_files || targetFiles,
4554
+ text: input.bundle.supportV5ScopeDigest || input.bundle.supportV5SupervisorState.currentGoal
4555
+ });
4556
+ const similarCaseSelection = diagnosisEvidencePack.similarCaseSelection;
4557
+ const similarCaseHintsText = similarCaseSelection?.ranked?.length
4558
+ ? [
4559
+ 'Similar accepted fix hints. Advisory only: use these to prioritize inspection paths, not as proof.',
4560
+ ...similarCaseSelection.ranked.slice(0, 5).map((hint, index) => [
4561
+ `${index + 1}. source=${hint.source || 'unknown'} score=${Number(hint.score || 0)}`,
4562
+ hint.ticketNumber ? `ticket=${hint.ticketNumber}` : '',
4563
+ hint.commitSha ? `commit=${hint.commitSha}` : '',
4564
+ hint.issueClass ? `issue_class=${hint.issueClass}` : '',
4565
+ hint.ownerFiles?.length ? `owner_files=${hint.ownerFiles.slice(0, 4).join(', ')}` : '',
4566
+ hint.reason ? `signals=${hint.reason}` : '',
4567
+ hint.title ? `title=${cleanText(hint.title, 180)}` : ''
4568
+ ].filter(Boolean).join(' | '))
4569
+ ].join('\n')
4570
+ : '';
4571
+ const qaRow = input.activeQaRow || activeMicrotask?.lane === 'qa'
4572
+ ? input.activeQaRow || laneMemory.activeQaRow || input.bundle.supportV5SupervisorState.currentQaRow
4573
+ : undefined;
4574
+ const sections = [
4575
+ {
4576
+ name: 'microtask_contract',
4577
+ text: [
4578
+ 'Support Runner V5 Microtask Contract.',
4579
+ 'Use the existing persistent lane thread. Do not start a fresh thread.',
4580
+ 'Do exactly one microtask and one proof gate. Do not replay ticket triage, prior plans, attachments, broad QA checklist, or unrelated scope.',
4581
+ 'If context is insufficient, request the smallest missing file/log/artifact by path and park this microtask; do not broaden the prompt.',
4582
+ 'Do not send customer email. Do not deploy. Do not spawn duplicate server/client/Mongo/browser/Codex processes.',
4583
+ diagnosisActive
4584
+ ? 'Diagnosis gate hard boundary: read-only investigation only. Do not edit source, generated files, package files, tests, fixtures, QA artifacts, or local data. Return the diagnosis JSON contract only.'
4585
+ : input.lane === 'qa'
4586
+ ? 'QA lane hard boundary: the platform preflight owns compile, dependency install, Mongo/server/client startup, and Angular startup. This lane owns only browser/data proof and QA artifacts unless the prompt explicitly says preflight was skipped.'
4587
+ : 'Build lane hard boundary: do not run `npm run build-dev`, `ng build`, `npm run server`, `npm run client`, `ng serve`, `run-local-qa.sh`, browser automation, or any watch/long-lived command. If a broad compile gate is needed, run only `npm run build-prod -- --watch=false`; otherwise use a targeted finite check or return the precise blocker.'
4588
+ ].join('\n')
4589
+ },
4590
+ diagnosisActive ? {
4591
+ name: 'root_cause_first_diagnosis_contract',
4592
+ text: [
4593
+ 'Before any product-code repair, produce strict JSON only:',
4594
+ '{"support_diagnosis_gate":{"issue_case":{"customer_complaint":"","expected_result":"","observed_result":"","route_module":"","account_customer_context":"","reproduction_status":"reproduced|blocked|classified","reproduction_blocker":""},"issue_class":"no_op_submit|missing_wrong_data|filter_query_mismatch|invoice_pdf_export|upload_import|route_auth_hydration|slow_query_performance","accepted_hypothesis":{"statement":"","falsifiable_test":"","evidence":[""]},"rejected_alternatives":[""],"failing_path":{"frontend":"","backend":"","shared_library":"","data_query":"","description":""},"owner_files":["small/exact/file.ts"],"proof_plan":{"before":"","before_state_unavailable_reason":"","action":"","after":"","business_assertion":"","route":"","data_assertion":"","artifact_expectation":"","business_proof_contract":{"issue_class":"same as diagnosis issue_class","setup_state":"","action_under_test":"","expected_business_state_change":"","prohibited_false_pass":"Route load, screenshot, scorecard, or model claim alone is not acceptance.","proof_artifacts":["browser trace/screenshot/json/mongo delta"],"data_or_dom_assertion":""}},"similar_tickets":[],"similar_commits":[],"evidence":[{"type":"ticket|browser|mongo|log|code|commit|qa|other","summary":"","artifactPath":""}],"status":"passed"}}',
4595
+ 'Owner files must be a small exact editable set, not directories, globs, generated wrappers, or broad repo areas.',
4596
+ 'The accepted hypothesis must be falsifiable and backed by evidence; prior similar tickets or commits are hints only and cannot bypass fresh diagnosis.',
4597
+ 'For reproduction_status="reproduced", evidence must include at least one browser, Mongo, log, or QA evidence object with artifactPath so the runner can replay the proof.',
4598
+ 'For reproduction_status="classified", evidence must still include non-ticket root-cause proof such as code, commit, Mongo, log, browser, or QA evidence.',
4599
+ 'Ticket text and similar-ticket hints are context, not root-cause proof by themselves.',
4600
+ 'The proof plan must be before/action/after business proof plus business_proof_contract. If before-state proof is impossible, explain exactly why in before_state_unavailable_reason.',
4601
+ 'The business_proof_contract must name the setup state, action under test, expected business state change, prohibited false pass, required artifacts, and DOM/data assertion.'
4602
+ ].join('\n')
4603
+ } : {
4604
+ name: 'diagnosis_gate_context',
4605
+ text: diagnosisValidation.valid && diagnosisGate ? [
4606
+ `Diagnosis issue class: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).issue_class}`,
4607
+ `Accepted hypothesis: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).accepted_hypothesis.statement}`,
4608
+ `Failing path: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).failing_path.description || (diagnosisGate as ResolveIOSupportDiagnosisGate).failing_path.frontend || (diagnosisGate as ResolveIOSupportDiagnosisGate).failing_path.backend || ''}`,
4609
+ `Owner files: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).owner_files.join(', ')}`,
4610
+ `Business proof required: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.business_assertion}`,
4611
+ (diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.business_proof_contract
4612
+ ? `Business proof contract: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.business_proof_contract?.action_under_test} -> ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.business_proof_contract?.expected_business_state_change}; assertion ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.business_proof_contract?.data_or_dom_assertion}`
4613
+ : '',
4614
+ `Before/action/after: ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.before || (diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.before_state_unavailable_reason} -> ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.action} -> ${(diagnosisGate as ResolveIOSupportDiagnosisGate).proof_plan.after}`
4615
+ ].filter(Boolean).join('\n') : 'SupportDiagnosisGate is not valid. Park instead of editing product code.'
4616
+ },
4617
+ diagnosisActive ? {
4618
+ name: 'diagnosis_evidence_pack',
4619
+ text: JSON.stringify({
4620
+ packId: diagnosisEvidencePack.packId,
4621
+ status: diagnosisEvidencePack.status,
4622
+ readOnly: diagnosisEvidencePack.readOnly,
4623
+ sourceEditsAllowed: diagnosisEvidencePack.sourceEditsAllowed,
4624
+ requiredOutputKey: diagnosisEvidencePack.requiredOutputKey,
4625
+ requiredFields: diagnosisEvidencePack.requiredFields,
4626
+ requiredEvidence: diagnosisEvidencePack.requiredEvidence,
4627
+ forbiddenActions: diagnosisEvidencePack.forbiddenActions,
4628
+ validationBlockers: diagnosisEvidencePack.validationBlockers,
4629
+ issueClassHint: diagnosisEvidencePack.issueClassHint,
4630
+ ownerFileHints: diagnosisEvidencePack.ownerFileHints,
4631
+ similarHintsAdvisoryOnly: true,
4632
+ similarHintCount: diagnosisEvidencePack.similarCaseSelection.ranked.length,
4633
+ issueClassProbeCatalog: diagnosisEvidencePack.issueClassProbeCatalog.map((entry) => ({
4634
+ issue_class: entry.issue_class,
4635
+ action: entry.action,
4636
+ proof_required: entry.proof_required,
4637
+ acceptance_gate: entry.acceptance_gate,
4638
+ false_pass_blockers: entry.false_pass_blockers
4639
+ }))
4640
+ })
4641
+ } : undefined,
4642
+ diagnosisActive && similarCaseHintsText ? {
4643
+ name: 'similar_accepted_fix_hints',
4644
+ text: similarCaseHintsText
4645
+ } : undefined,
4646
+ {
4647
+ name: 'scope_digest',
4648
+ text: cleanText(input.bundle.supportV5ScopeDigest || input.bundle.supportV5SupervisorState.approvedScope, 900)
4649
+ },
4650
+ {
4651
+ name: 'active_microtask',
4652
+ text: activeMicrotask ? [
4653
+ `Microtask id: ${activeMicrotask.microtaskId}`,
4654
+ `Lane: ${input.lane}`,
4655
+ `Type: ${activeMicrotask.type}`,
4656
+ `Status: ${activeMicrotask.status}`,
4657
+ `Objective: ${cleanText(activeMicrotask.objective, 360)}`,
4658
+ `Self-gate: ${cleanText(activeMicrotask.selfGate, 260)}`,
4659
+ `Acceptance proof: ${cleanText(activeMicrotask.acceptanceProof, 260)}`,
4660
+ `Attempts on this microtask: ${activeMicrotask.attempts}`,
4661
+ `Thread key: ${taskThreadKey}`
4662
+ ].join('\n') : 'No active microtask found. Park and report support_v5_microtask_missing.'
4663
+ },
4664
+ {
4665
+ name: 'failure_delta',
4666
+ text: input.failureText ? `Latest blocker/failure delta: ${cleanText(input.failureText, 700)}` : ''
4667
+ },
4668
+ {
4669
+ name: 'target_context',
4670
+ text: [
4671
+ targetFiles.length ? `Target files: ${targetFiles.join(', ')}` : '',
4672
+ changedFiles.length ? `Changed files: ${changedFiles.join(', ')}` : '',
4673
+ artifactPaths.length ? `Artifacts: ${artifactPaths.join(', ')}` : '',
4674
+ qaRow?.workflow ? `QA row workflow: ${cleanText(qaRow.workflow, 220)}` : '',
4675
+ qaRow?.route ? `QA row route: ${cleanText(qaRow.route, 220)}` : '',
4676
+ qaRow?.assertion ? `QA row assertion: ${cleanText(qaRow.assertion, 260)}` : '',
4677
+ contextSnippets.length ? `Relevant snippets:\n${contextSnippets.join('\n---\n')}` : ''
4678
+ ].filter(Boolean).join('\n')
4679
+ },
4680
+ input.lane === 'qa' ? {
4681
+ name: 'qa_browser_evidence_contract',
4682
+ text: [
4683
+ 'QA lane first action after preflight: collect fresh browser evidence for the active row, not a proof review of existing route/auth artifacts.',
4684
+ 'Use the already-running localhost harness and staged env. Before any shell command, run `source .resolveio-support-tools/env.sh 2>/dev/null || source ../.resolveio-support-tools/env.sh 2>/dev/null || true`; then use `$RESOLVEIO_SUPPORT_QA_CLIENT_URL`, `$RESOLVEIO_SUPPORT_QA_SERVER_URL`, `$PUPPETEER_EXECUTABLE_PATH`, and `$CHROME_BIN`.',
4685
+ 'Required auth replay helper inside every Puppeteer row script: `const storage=JSON.parse(fs.readFileSync("qa-artifacts/auth-bootstrap-storage-state.json","utf8")); const entries=[...(Array.isArray(storage.localStorageEntries)?storage.localStorageEntries:[]), ...(Array.isArray(storage.localStorage)?storage.localStorage:Object.entries(storage.localStorage||{}).map(([key,value])=>({key,value}))), ...((storage.origins||[]).flatMap(o=>o.localStorage||[]))].map(e=>({key:e.key||e.name,value:e.value})).filter(e=>e.key); const clientUrl=process.env.RESOLVEIO_SUPPORT_QA_CLIENT_URL||process.env.RESOLVEIO_RUNNER_QA_CLIENT_URL; const rowRoute="<ACTIVE_QA_ROUTE>"; await page.goto(clientUrl,{waitUntil:"domcontentloaded"}); await page.evaluate(items=>{localStorage.clear(); for(const item of items)localStorage.setItem(item.key,item.value);}, entries); await page.goto(new URL(rowRoute, clientUrl).href,{waitUntil:"domcontentloaded"});` Replace `<ACTIVE_QA_ROUTE>` with the active row route before running. A screenshot of `/home`, the login screen, or a shell route after this helper means the QA script navigated to the wrong route or storage is stale; rerun the same row with the active route before returning an app blocker.',
4686
+ 'Allowed first command shape: one bounded `node`/Puppeteer row script that reads `qa-artifacts/auth-bootstrap-storage-state.json`, opens `$RESOLVEIO_SUPPORT_QA_CLIENT_URL`, seeds localStorage, navigates to the active QA route with `new URL(activeRoute, clientUrl).href`, drives the visible workflow, captures screenshot/caption, and updates the active matrix row. A `/home` proof is invalid for a locked non-home route.',
4687
+ 'Forbidden in QA lane after preflight: `npm run build`, `npm run build-dev`, `npm run build-prod`, `ng build`, `npm install`, `npm ci`, `npm run server`, `npm run client`, `ng serve`, `gulp`, `mongod`, `run-local-qa.sh`, and broad source discovery. If a build/startup/dependency problem is suspected, return `blocked` or `needs-fix` with the existing qa-artifacts log paths instead of launching those commands.',
4688
+ 'Launch Puppeteer from `PUPPETEER_EXECUTABLE_PATH || CHROME_BIN`, seed localStorage from `qa-artifacts/auth-bootstrap-storage-state.json`, then navigate to the active QA route.',
4689
+ 'Before passing, confirm `qa-artifacts/auth-bootstrap-result.json` used the ticket reporter or named affected user when available. If it used generic admin/dev while `qa-live-data-seed-result.json.selected.qa_user_context` names a reporter/affected user, rerun auth as that user or return needs-fix.',
4690
+ 'Drive the visible customer workflow for this one row. Capture a new customer-facing screenshot/caption and update `qa-artifacts/qa-coverage-matrix.json` with workflow, route, data id/name, assertion, screenshot path, caption, and pass/failed/blocked status.',
4691
+ 'Also write `qa-artifacts/aiqa-business-assertion.json` with one AIQaBusinessAssertion object: assertion, status, workflow, route, before, action, expected, after/observed, dataProof or mongoDelta, artifactPaths, and metadata.supportDiagnosisProof=true only when it maps to the active SupportDiagnosisGate proof_plan.',
4692
+ 'After selecting any From/To, source/target, dropdown, combobox, rio-select, filter, item, customer, yard, chemical, or treatment-plan value, read the visible control text back from the DOM. If it still contains placeholder text such as Select Chemical, Select Yard, Select Customer, Select Treatment Plan Type, Loading..., empty, null, or undefined, do not click the action button and do not mark pass. Capture the selected-state blocker screenshot/DOM text and return needs-fix.',
4693
+ 'If the row needs persisted data proof, create that proof yourself in the same bounded QA command by querying localhost QA Mongo through process.env.MONGO_URL after the visible UI action. Write the result under qa-artifacts/ as JSON and reference that path in the matrix. Do not ask for a missing post-action DB artifact while the local QA stack is running; either write it or return needs-fix with the exact query/script error. For update-interchangeables rows, the JSON must include concrete non-placeholder fromText and toText, before/after counts or sample documents for treatment plans and tank/interchangeable records, and must fail if either selected value is only an arrow/placeholder or if the persisted collections are empty.',
4694
+ 'If the row names multiple concrete entities such as assets, units, BOLs, invoices, chemicals, customers, yards, or numbered records, prove every named entity. Do not pass by proving only one representative record.',
4695
+ 'For customer-reported data bugs, use production-seeded localhost records from `qa-live-data-seed-result.json`. If the exact named record is missing from the seed, fail the row as a seed/query blocker instead of testing a substitute record.',
4696
+ 'Route-ready, auth-bootstrap, shell, header-only, and empty-list screenshots do not satisfy this microtask. If the row cannot be proven, capture the blocker screenshot/DOM state and return needs-fix with that exact blocker.',
4697
+ 'Do not run compile/startup/npm install, do not spawn another server/client/Mongo, and do not inspect broad source trees before the first browser evidence command.'
4698
+ ].join('\n')
4699
+ } : {
4700
+ name: 'non_qa_noop',
4701
+ text: ''
4702
+ },
4703
+ {
4704
+ name: 'return_contract',
4705
+ text: diagnosisActive
4706
+ ? 'Return strict JSON only with support_diagnosis_gate. Do not include Markdown and do not edit files.'
4707
+ : input.lane === 'qa'
4708
+ ? 'Return strict JSON only: {"status":"pass"|"needs-fix"|"blocked","microtaskId":"","summary":"","evidence":[""],"artifacts":[""],"next_actions":[""]}.'
4709
+ : 'Return concise Markdown with: Microtask Result, Root Cause, Changes, Self-Gate, Acceptance Proof, Residual Risk.'
4710
+ }
4711
+ ].filter((section): section is { name: string; text: string } => !!section && !!cleanText(section.text, 20));
4712
+ const promptSections = sections.map((section) => ({
4713
+ name: section.name,
4714
+ tokenEstimate: estimateTextTokens(section.text)
4715
+ }));
4716
+ const prompt = sections.map((section) => section.text).join('\n\n');
4717
+ const promptTokenEstimate = estimateTextTokens(prompt);
4718
+ return {
4719
+ prompt,
4720
+ promptTokenEstimate,
4721
+ promptSections,
4722
+ diagnosisEvidencePack,
4723
+ activeMicrotask,
4724
+ withinCap: promptTokenEstimate <= cap,
4725
+ withinHardCap: promptTokenEstimate <= hardCap,
4726
+ cap,
4727
+ hardCap,
4728
+ reason: promptTokenEstimate > hardCap
4729
+ ? 'support_v5_microtask_prompt_hard_cap'
4730
+ : promptTokenEstimate > cap
4731
+ ? 'support_v5_microtask_prompt_soft_cap'
4732
+ : undefined
4733
+ };
4734
+ }
4735
+
4736
+ export function summarizeResolveIOSupportV5MicrotaskUsage(bundle: ResolveIOSupportV5StateBundle): {
4737
+ totalPromptTokenEstimate: number;
4738
+ byMicrotask: Array<{ microtaskId: string; promptTokenEstimate: number; calls: number }>;
4739
+ bySection: ResolveIOSupportV5UsageSection[];
4740
+ broadPromptViolations: ResolveIOSupportV5MicrotaskUsage[];
4741
+ } {
4742
+ const byMicrotask = new Map<string, { microtaskId: string; promptTokenEstimate: number; calls: number }>();
4743
+ const bySection = new Map<string, number>();
4744
+ let totalPromptTokenEstimate = 0;
4745
+ const broadPromptViolations: ResolveIOSupportV5MicrotaskUsage[] = [];
4746
+ const promptBudget = buildResolveIOSupportV5PromptBudget();
4747
+ for (const usage of bundle.supportV5MicrotaskUsageHistory || []) {
4748
+ totalPromptTokenEstimate += usage.promptTokenEstimate || 0;
4749
+ const existing = byMicrotask.get(usage.microtaskId) || { microtaskId: usage.microtaskId, promptTokenEstimate: 0, calls: 0 };
4750
+ existing.promptTokenEstimate += usage.promptTokenEstimate || 0;
4751
+ existing.calls += 1;
4752
+ byMicrotask.set(usage.microtaskId, existing);
4753
+ for (const section of usage.promptSections || []) {
4754
+ bySection.set(section.name, (bySection.get(section.name) || 0) + section.tokenEstimate);
4755
+ }
4756
+ const hardCap = usage.lane === 'qa' ? promptBudget.qaMicrotaskHardCap : promptBudget.buildMicrotaskHardCap;
4757
+ if ((usage.promptTokenEstimate || 0) > hardCap) {
4758
+ broadPromptViolations.push(usage);
4759
+ }
4760
+ }
4761
+ return {
4762
+ totalPromptTokenEstimate,
4763
+ byMicrotask: Array.from(byMicrotask.values()),
4764
+ bySection: Array.from(bySection.entries()).map(([name, tokenEstimate]) => ({ name, tokenEstimate })),
4765
+ broadPromptViolations
4766
+ };
4767
+ }
4768
+
4769
+ export function buildResolveIOSupportV5Incident(input: {
4770
+ incidentClass: string;
4771
+ summary: string;
4772
+ stepType?: ResolveIOSupportV5StepType;
4773
+ blocker?: string;
4774
+ artifactPaths?: string[];
4775
+ now?: Date | string;
4776
+ }): ResolveIOSupportV5RunnerIncident {
4777
+ return {
4778
+ incidentClass: cleanText(input.incidentClass, 120) || 'support_v5_runner_incident',
4779
+ summary: cleanText(input.summary, 1200),
4780
+ stepType: input.stepType,
4781
+ blockerFingerprint: input.blocker ? fingerprintResolveIOSupportV5Blocker(input.blocker) : undefined,
4782
+ artifactPaths: cleanList(input.artifactPaths, 40, 500),
4783
+ recordedAt: isoNow(input.now)
4784
+ };
4785
+ }