create-principles-disciple 1.144.26 → 1.145.0

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 (343) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/codex-adapter/dist/codex-evidence-recorder.js +8 -2
  3. package/codex-adapter/dist/index.d.ts +2 -2
  4. package/codex-adapter/dist/index.js +1 -1
  5. package/codex-adapter/dist/ingestion/admission.js +16 -0
  6. package/codex-adapter/dist/ingestion/codex-version.d.ts +10 -4
  7. package/codex-adapter/dist/ingestion/codex-version.js +10 -4
  8. package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +48 -0
  9. package/codex-adapter/dist/ingestion/explicit-owner-statement.js +123 -0
  10. package/codex-adapter/dist/ingestion/ingestion.js +50 -1
  11. package/codex-adapter/dist/pd-hook.js +66 -3
  12. package/codex-adapter/dist/worker/workspace-worker.d.ts +14 -0
  13. package/codex-adapter/dist/worker/workspace-worker.js +194 -11
  14. package/codex-adapter/package.json +2 -2
  15. package/console/dist/server/models/ApprovalsConsoleModel.d.ts +2 -2
  16. package/console/dist/server/models/ApprovalsConsoleModel.js +1 -1
  17. package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +1 -1
  18. package/console/dist/server/models/PrinciplesConsoleModel.d.ts +26 -1
  19. package/console/dist/server/models/PrinciplesConsoleModel.js +18 -1
  20. package/console/dist/server/routes/activations.js +4 -4
  21. package/console/dist/server/routes/approvals.js +2 -2
  22. package/console/dist/server/routes/failed-tasks.d.ts +9 -0
  23. package/console/dist/server/routes/failed-tasks.js +116 -7
  24. package/console/dist/server/routes/principles.js +73 -1
  25. package/console/dist/shared/prompt-injection-contract.d.ts +1 -1
  26. package/console/dist/ui/api.d.ts +5 -5
  27. package/console/dist/ui/i18n/en.json +13 -2
  28. package/console/dist/ui/i18n/zh-CN.json +13 -2
  29. package/console/dist/ui/pages/activation/ActivationPage.js +2 -1
  30. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +42 -4
  31. package/console/dist/ui/pages/focus/FocusPage.js +3 -3
  32. package/console/dist/ui/pages/principles/PrinciplesPage.js +7 -2
  33. package/console/dist/ui/utils/validators.d.ts +6 -2
  34. package/console/dist/ui/utils/validators.js +19 -4
  35. package/console/dist/web/assets/app.css +26 -0
  36. package/console/dist/web/assets/app.js +24 -24
  37. package/console/package.json +4 -4
  38. package/core/dist/host/host-installer.d.ts +3 -1
  39. package/core/dist/host/host-installer.d.ts.map +1 -1
  40. package/core/dist/host/host-installer.js.map +1 -1
  41. package/core/dist/runtime-v2/admission-gate.d.ts +8 -0
  42. package/core/dist/runtime-v2/admission-gate.d.ts.map +1 -1
  43. package/core/dist/runtime-v2/admission-gate.js +3 -2
  44. package/core/dist/runtime-v2/admission-gate.js.map +1 -1
  45. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  46. package/core/dist/runtime-v2/config/pd-config-defaults.js +16 -1
  47. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  48. package/core/dist/runtime-v2/context-payload.d.ts +43 -3
  49. package/core/dist/runtime-v2/context-payload.d.ts.map +1 -1
  50. package/core/dist/runtime-v2/context-payload.js +15 -1
  51. package/core/dist/runtime-v2/context-payload.js.map +1 -1
  52. package/core/dist/runtime-v2/evidence-guards.d.ts +6 -0
  53. package/core/dist/runtime-v2/evidence-guards.d.ts.map +1 -1
  54. package/core/dist/runtime-v2/evidence-guards.js +11 -0
  55. package/core/dist/runtime-v2/evidence-guards.js.map +1 -1
  56. package/core/dist/runtime-v2/evolution/evolution-types.d.ts +4 -4
  57. package/core/dist/runtime-v2/evolution/evolution-types.d.ts.map +1 -1
  58. package/core/dist/runtime-v2/evolution/evolution-types.js +1 -1
  59. package/core/dist/runtime-v2/evolution/evolution-types.js.map +1 -1
  60. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  61. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +6 -0
  62. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  63. package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.d.ts.map +1 -1
  64. package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +8 -0
  65. package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
  66. package/core/dist/runtime-v2/index.d.ts +5 -2
  67. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  68. package/core/dist/runtime-v2/index.js +3 -1
  69. package/core/dist/runtime-v2/index.js.map +1 -1
  70. package/core/dist/runtime-v2/internalization/adversarial-feedback.d.ts +3 -1
  71. package/core/dist/runtime-v2/internalization/adversarial-feedback.d.ts.map +1 -1
  72. package/core/dist/runtime-v2/internalization/adversarial-feedback.js +10 -1
  73. package/core/dist/runtime-v2/internalization/adversarial-feedback.js.map +1 -1
  74. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +11 -0
  75. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  76. package/core/dist/runtime-v2/internalization/evaluator-output.js +10 -0
  77. package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  78. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +3 -3
  79. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -5
  81. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  82. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  83. package/core/dist/runtime-v2/internalization/evaluator-runner.js +13 -16
  84. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  85. package/core/dist/runtime-v2/internalization/owner-decision-review.d.ts.map +1 -1
  86. package/core/dist/runtime-v2/internalization/owner-decision-review.js +5 -1
  87. package/core/dist/runtime-v2/internalization/owner-decision-review.js.map +1 -1
  88. package/core/dist/runtime-v2/internalization/owner-review.d.ts +2 -2
  89. package/core/dist/runtime-v2/internalization/owner-review.js +2 -2
  90. package/core/dist/runtime-v2/internalization/scribe-output.d.ts +12 -0
  91. package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
  92. package/core/dist/runtime-v2/internalization/scribe-output.js +20 -0
  93. package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
  94. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +32 -1
  95. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
  96. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js +38 -1
  97. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
  98. package/core/dist/runtime-v2/internalization-queue-read-model.d.ts +6 -0
  99. package/core/dist/runtime-v2/internalization-queue-read-model.d.ts.map +1 -1
  100. package/core/dist/runtime-v2/internalization-queue-read-model.js +8 -1
  101. package/core/dist/runtime-v2/internalization-queue-read-model.js.map +1 -1
  102. package/core/dist/runtime-v2/intervention-evidence-normalizer.d.ts.map +1 -1
  103. package/core/dist/runtime-v2/intervention-evidence-normalizer.js +10 -3
  104. package/core/dist/runtime-v2/intervention-evidence-normalizer.js.map +1 -1
  105. package/core/dist/runtime-v2/pain-ingress.d.ts +1 -1
  106. package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
  107. package/core/dist/runtime-v2/pain-signal-bridge.d.ts +68 -2
  108. package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  109. package/core/dist/runtime-v2/pain-signal-bridge.js +541 -18
  110. package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  111. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  112. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +4 -0
  113. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  114. package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts +2 -2
  115. package/core/dist/runtime-v2/product-telemetry/snapshot-contract.d.ts.map +1 -1
  116. package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js +2 -2
  117. package/core/dist/runtime-v2/product-telemetry/snapshot-contract.js.map +1 -1
  118. package/core/dist/runtime-v2/store/candidate/admission-document.d.ts +90 -0
  119. package/core/dist/runtime-v2/store/candidate/admission-document.d.ts.map +1 -0
  120. package/core/dist/runtime-v2/store/candidate/admission-document.js +142 -0
  121. package/core/dist/runtime-v2/store/candidate/admission-document.js.map +1 -0
  122. package/core/dist/runtime-v2/store/candidate/candidate-store.d.ts +53 -0
  123. package/core/dist/runtime-v2/store/candidate/candidate-store.d.ts.map +1 -1
  124. package/core/dist/runtime-v2/store/candidate/memory-candidate-store.d.ts +35 -0
  125. package/core/dist/runtime-v2/store/candidate/memory-candidate-store.d.ts.map +1 -1
  126. package/core/dist/runtime-v2/store/candidate/memory-candidate-store.js +48 -0
  127. package/core/dist/runtime-v2/store/candidate/memory-candidate-store.js.map +1 -1
  128. package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.d.ts +44 -0
  129. package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.d.ts.map +1 -1
  130. package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js +136 -0
  131. package/core/dist/runtime-v2/store/candidate/sqlite-candidate-store.js.map +1 -1
  132. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
  133. package/core/dist/runtime-v2/store/intervention-evidence-schema.js +1 -1
  134. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts +65 -0
  135. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  136. package/core/dist/runtime-v2/store/runtime-state-manager.js +46 -0
  137. package/core/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  138. package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  139. package/core/dist/runtime-v2/store/sqlite-connection.js +42 -0
  140. package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  141. package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.d.ts +1 -1
  142. package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.d.ts.map +1 -1
  143. package/core/dist/runtime-v2/store/sqlite-intervention-evidence-store.js.map +1 -1
  144. package/core/dist/runtime-v2/store/task/sqlite-family-reopen.d.ts +19 -0
  145. package/core/dist/runtime-v2/store/task/sqlite-family-reopen.d.ts.map +1 -0
  146. package/core/dist/runtime-v2/store/task/sqlite-family-reopen.js +36 -0
  147. package/core/dist/runtime-v2/store/task/sqlite-family-reopen.js.map +1 -0
  148. package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts +23 -0
  149. package/core/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
  150. package/core/dist/runtime-v2/store/task/sqlite-task-store.js +50 -0
  151. package/core/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
  152. package/core/dist/runtime-v2/task-status.d.ts +3 -1
  153. package/core/dist/runtime-v2/task-status.d.ts.map +1 -1
  154. package/core/dist/runtime-v2/task-status.js +1 -1
  155. package/core/dist/runtime-v2/task-status.js.map +1 -1
  156. package/core/dist/runtime-v2/trajectory-schema.d.ts.map +1 -1
  157. package/core/dist/runtime-v2/trajectory-schema.js +11 -0
  158. package/core/dist/runtime-v2/trajectory-schema.js.map +1 -1
  159. package/core/dist/runtime-v2/types/event-types.d.ts +2 -2
  160. package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
  161. package/core/dist/runtime-v2/types/event-types.js +1 -1
  162. package/core/dist/runtime-v2/types/event-types.js.map +1 -1
  163. package/core/dist/runtime-v2/types/evidence-chain-contract.d.ts +1 -1
  164. package/core/dist/runtime-v2/types/evidence-chain-contract.d.ts.map +1 -1
  165. package/core/dist/runtime-v2/types/evidence-chain-contract.js +2 -2
  166. package/core/dist/runtime-v2/types/evidence-chain-contract.js.map +1 -1
  167. package/core/dist/runtime-v2/types/intervention-evidence-contract.d.ts +3 -3
  168. package/core/dist/runtime-v2/types/intervention-evidence-contract.d.ts.map +1 -1
  169. package/core/dist/runtime-v2/types/intervention-evidence-contract.js +1 -0
  170. package/core/dist/runtime-v2/types/intervention-evidence-contract.js.map +1 -1
  171. package/core/dist/telemetry-event.d.ts +2 -2
  172. package/core/dist/telemetry-event.d.ts.map +1 -1
  173. package/core/dist/telemetry-event.js +4 -0
  174. package/core/dist/telemetry-event.js.map +1 -1
  175. package/core/dist/trajectory-types.d.ts +2 -2
  176. package/core/dist/trajectory-types.d.ts.map +1 -1
  177. package/core/package.json +3 -3
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +84 -8
  180. package/dist/index.js.map +1 -1
  181. package/dist/installer.d.ts +42 -11
  182. package/dist/installer.d.ts.map +1 -1
  183. package/dist/installer.js +140 -36
  184. package/dist/installer.js.map +1 -1
  185. package/dist/installers/index.d.ts +18 -7
  186. package/dist/installers/index.d.ts.map +1 -1
  187. package/dist/installers/index.js +13 -4
  188. package/dist/installers/index.js.map +1 -1
  189. package/dist/installers/workbuddy-host-installer.d.ts +246 -0
  190. package/dist/installers/workbuddy-host-installer.d.ts.map +1 -0
  191. package/dist/installers/workbuddy-host-installer.js +1419 -0
  192. package/dist/installers/workbuddy-host-installer.js.map +1 -0
  193. package/dist/mvp-config.d.ts +24 -0
  194. package/dist/mvp-config.d.ts.map +1 -1
  195. package/dist/mvp-config.js +36 -0
  196. package/dist/mvp-config.js.map +1 -1
  197. package/dist/prompts.d.ts +13 -0
  198. package/dist/prompts.d.ts.map +1 -1
  199. package/dist/prompts.js +45 -8
  200. package/dist/prompts.js.map +1 -1
  201. package/dist/uninstaller.d.ts +1 -1
  202. package/dist/uninstaller.d.ts.map +1 -1
  203. package/dist/uninstaller.js +29 -4
  204. package/dist/uninstaller.js.map +1 -1
  205. package/host-runtime/dist/governance-signal-admission.d.ts +63 -0
  206. package/host-runtime/dist/governance-signal-admission.js +322 -53
  207. package/host-runtime/dist/host-liveness-contract.js +7 -1
  208. package/host-runtime/dist/index.d.ts +1 -0
  209. package/host-runtime/dist/index.js +5 -1
  210. package/host-runtime/dist/internalization-consumer-cycle.d.ts +12 -0
  211. package/host-runtime/dist/internalization-consumer-cycle.js +43 -1
  212. package/host-runtime/dist/intervention-evidence-ingress.d.ts +1 -1
  213. package/host-runtime/dist/product-telemetry/service.js +1 -1
  214. package/host-runtime/dist/production-pain-evidence.d.ts +59 -1
  215. package/host-runtime/dist/production-pain-evidence.js +200 -17
  216. package/host-runtime/dist/prompt-activation-deliverability.js +5 -4
  217. package/host-runtime/dist/prompt-injection-event-evidence.d.ts +1 -1
  218. package/host-runtime/dist/prompt-injection-event-evidence.js +5 -1
  219. package/host-runtime/dist/prompt-injection-projection.d.ts +14 -5
  220. package/host-runtime/dist/prompt-injection-projection.js +30 -16
  221. package/host-runtime/dist/workbuddy-worker-status.d.ts +24 -0
  222. package/host-runtime/dist/workbuddy-worker-status.js +52 -0
  223. package/host-runtime/package.json +3 -3
  224. package/install-layout/dist/index.d.ts +18 -1
  225. package/install-layout/dist/index.js +15 -2
  226. package/install-layout/package.json +1 -1
  227. package/package.json +6 -5
  228. package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
  229. package/pd-cli/dist/commands/candidate.js +44 -4
  230. package/pd-cli/dist/commands/candidate.js.map +1 -1
  231. package/pd-cli/dist/commands/codex-worker.d.ts.map +1 -1
  232. package/pd-cli/dist/commands/codex-worker.js +30 -1
  233. package/pd-cli/dist/commands/codex-worker.js.map +1 -1
  234. package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -1
  235. package/pd-cli/dist/commands/config-doctor.js +19 -4
  236. package/pd-cli/dist/commands/config-doctor.js.map +1 -1
  237. package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
  238. package/pd-cli/dist/commands/diagnose.js +4 -1
  239. package/pd-cli/dist/commands/diagnose.js.map +1 -1
  240. package/pd-cli/dist/commands/health-codex-install.d.ts +85 -0
  241. package/pd-cli/dist/commands/health-codex-install.d.ts.map +1 -0
  242. package/pd-cli/dist/commands/health-codex-install.js +207 -0
  243. package/pd-cli/dist/commands/health-codex-install.js.map +1 -0
  244. package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
  245. package/pd-cli/dist/commands/health-codex.js +78 -0
  246. package/pd-cli/dist/commands/health-codex.js.map +1 -1
  247. package/pd-cli/dist/commands/pain-list.d.ts +3 -2
  248. package/pd-cli/dist/commands/pain-list.d.ts.map +1 -1
  249. package/pd-cli/dist/commands/pain-list.js +10 -10
  250. package/pd-cli/dist/commands/pain-list.js.map +1 -1
  251. package/pd-cli/dist/commands/pain-record.d.ts +2 -0
  252. package/pd-cli/dist/commands/pain-record.d.ts.map +1 -1
  253. package/pd-cli/dist/commands/pain-record.js +36 -0
  254. package/pd-cli/dist/commands/pain-record.js.map +1 -1
  255. package/pd-cli/dist/commands/pain-retry.d.ts +6 -0
  256. package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
  257. package/pd-cli/dist/commands/pain-retry.js +152 -5
  258. package/pd-cli/dist/commands/pain-retry.js.map +1 -1
  259. package/pd-cli/dist/commands/runtime-activation.d.ts +1 -1
  260. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  261. package/pd-cli/dist/commands/runtime-activation.js +3 -3
  262. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  263. package/pd-cli/dist/commands/workbuddy-worker.d.ts +19 -0
  264. package/pd-cli/dist/commands/workbuddy-worker.d.ts.map +1 -0
  265. package/pd-cli/dist/commands/workbuddy-worker.js +168 -0
  266. package/pd-cli/dist/commands/workbuddy-worker.js.map +1 -0
  267. package/pd-cli/dist/index.js +29 -1
  268. package/pd-cli/dist/index.js.map +1 -1
  269. package/pd-cli/dist/resolve-workspace.d.ts +14 -2
  270. package/pd-cli/dist/resolve-workspace.d.ts.map +1 -1
  271. package/pd-cli/dist/resolve-workspace.js +88 -21
  272. package/pd-cli/dist/resolve-workspace.js.map +1 -1
  273. package/pd-cli/dist/services/admission-write-gate.d.ts +24 -0
  274. package/pd-cli/dist/services/admission-write-gate.d.ts.map +1 -0
  275. package/pd-cli/dist/services/admission-write-gate.js +41 -0
  276. package/pd-cli/dist/services/admission-write-gate.js.map +1 -0
  277. package/pd-cli/dist/services/pd-config-loader.d.ts +39 -7
  278. package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -1
  279. package/pd-cli/dist/services/pd-config-loader.js +111 -18
  280. package/pd-cli/dist/services/pd-config-loader.js.map +1 -1
  281. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +10 -0
  282. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  283. package/pd-cli/dist/services/rulehost-pipeline-runner.js +59 -4
  284. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  285. package/pd-cli/package.json +4 -3
  286. package/plugin/dist/bundle.js +564 -509
  287. package/plugin/dist/governance-audit.js +1 -1
  288. package/plugin/dist/openclaw.plugin.json +1 -1
  289. package/plugin/dist/rulehost-evidence.js +1 -1
  290. package/plugin/openclaw.plugin.json +1 -1
  291. package/plugin/package.json +3 -3
  292. package/release-manager/dist/index.d.ts.map +1 -1
  293. package/release-manager/dist/index.js +84 -8
  294. package/release-manager/dist/index.js.map +1 -1
  295. package/release-manager/dist/installer.d.ts +42 -11
  296. package/release-manager/dist/installer.d.ts.map +1 -1
  297. package/release-manager/dist/installer.js +140 -36
  298. package/release-manager/dist/installer.js.map +1 -1
  299. package/release-manager/dist/installers/index.d.ts +18 -7
  300. package/release-manager/dist/installers/index.d.ts.map +1 -1
  301. package/release-manager/dist/installers/index.js +13 -4
  302. package/release-manager/dist/installers/index.js.map +1 -1
  303. package/release-manager/dist/installers/workbuddy-host-installer.d.ts +246 -0
  304. package/release-manager/dist/installers/workbuddy-host-installer.d.ts.map +1 -0
  305. package/release-manager/dist/installers/workbuddy-host-installer.js +1419 -0
  306. package/release-manager/dist/installers/workbuddy-host-installer.js.map +1 -0
  307. package/release-manager/dist/mvp-config.d.ts +24 -0
  308. package/release-manager/dist/mvp-config.d.ts.map +1 -1
  309. package/release-manager/dist/mvp-config.js +36 -0
  310. package/release-manager/dist/mvp-config.js.map +1 -1
  311. package/release-manager/dist/prompts.d.ts +13 -0
  312. package/release-manager/dist/prompts.d.ts.map +1 -1
  313. package/release-manager/dist/prompts.js +45 -8
  314. package/release-manager/dist/prompts.js.map +1 -1
  315. package/release-manager/dist/uninstaller.d.ts +1 -1
  316. package/release-manager/dist/uninstaller.d.ts.map +1 -1
  317. package/release-manager/dist/uninstaller.js +29 -4
  318. package/release-manager/dist/uninstaller.js.map +1 -1
  319. package/release-manager/package.json +5 -4
  320. package/workbuddy-adapter/dist/codec/index.d.ts +2 -0
  321. package/workbuddy-adapter/dist/codec/index.js +2 -0
  322. package/workbuddy-adapter/dist/codec/input-decoder.d.ts +30 -0
  323. package/workbuddy-adapter/dist/codec/input-decoder.js +232 -0
  324. package/workbuddy-adapter/dist/codec/output-encoder.d.ts +62 -0
  325. package/workbuddy-adapter/dist/codec/output-encoder.js +75 -0
  326. package/workbuddy-adapter/dist/host-adapter.d.ts +33 -0
  327. package/workbuddy-adapter/dist/host-adapter.js +23 -0
  328. package/workbuddy-adapter/dist/index.d.ts +16 -0
  329. package/workbuddy-adapter/dist/index.js +16 -0
  330. package/workbuddy-adapter/dist/pd-hook.d.ts +17 -0
  331. package/workbuddy-adapter/dist/pd-hook.js +505 -0
  332. package/workbuddy-adapter/dist/tool-semantics.d.ts +22 -0
  333. package/workbuddy-adapter/dist/tool-semantics.js +33 -0
  334. package/workbuddy-adapter/dist/workbuddy-evidence-recorder.d.ts +50 -0
  335. package/workbuddy-adapter/dist/workbuddy-evidence-recorder.js +238 -0
  336. package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +41 -0
  337. package/workbuddy-adapter/dist/worker/workspace-worker.js +219 -0
  338. package/workbuddy-adapter/dist/workspace-resolution.d.ts +15 -0
  339. package/workbuddy-adapter/dist/workspace-resolution.js +53 -0
  340. package/workbuddy-adapter/package.json +62 -0
  341. package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +6 -0
  342. package/workbuddy-adapter/plugin/hooks/hooks.json +62 -0
  343. package/workbuddy-adapter/plugin/scripts/wrapper.cjs +79 -0
@@ -1,7 +1,8 @@
1
1
  import { DiagnosticianOutputV1Schema } from './diagnostician-output.js';
2
2
  import { Value } from '@sinclair/typebox/value';
3
3
  import { evaluateCandidateAdmissions, normalizePainProvenance } from './admission-gate.js';
4
- import { shouldShortCircuitEmptyEvidence } from './evidence-guards.js';
4
+ import { shouldShortCircuitEmptyEvidence, isOwnerStatementEvidence } from './evidence-guards.js';
5
+ import { ADMISSION_POLICY_VERSION, fingerprintMaterial, fingerprintStatement, } from './store/candidate/admission-document.js';
5
6
  import { parseRootCauseCategory } from './store/pain-diagnosis/pain-diagnosis-store.js';
6
7
  import { buildDreamerSeedFromCandidate, findExistingDreamerTask, ROUTE_CHANNEL_MAP, CANDIDATE_KIND_TO_ROUTE } from './internalization/intake-to-internalization-bridge.js';
7
8
  import { isRetryWaitBackoffElapsed } from './internalization/internalization-task-guards.js';
@@ -19,6 +20,8 @@ export const MAX_EVIDENCE_NOTE_CHARS = 200;
19
20
  * point of the field is that the Owner's own words survive verbatim.
20
21
  */
21
22
  export const MAX_CORRECTION_EVIDENCE_TEXT_CHARS = 2000;
23
+ /** SPEC-CODEX-PAIN-CLOSURE-001 CR-05 (§9.2): automatic post-processing continuation budget. */
24
+ export const MAX_POST_PROCESSING_ATTEMPTS = 3;
22
25
  /**
23
26
  * PRI-844: sanitize (strip PD tags / redact tokens / redact absolute paths)
24
27
  * without the 200-char generic evidence cap, then apply the defensive
@@ -105,6 +108,14 @@ function validatePersistedIngressFacts(diagnosticJson) {
105
108
  return { provenance: null, evidenceCount: null, errorCode: 'diagnostic_payload_invalid:not_an_object' };
106
109
  }
107
110
  const record = parsed;
111
+ // CR-02: the persisted correction evidence (including an explicit Owner
112
+ // statement) is part of the re-entry material — validated, never fabricated.
113
+ const persistedCorrection = record.correctionEvidence;
114
+ const correctionEvidence = typeof persistedCorrection === 'object' && persistedCorrection !== null && !Array.isArray(persistedCorrection)
115
+ && typeof persistedCorrection.text === 'string'
116
+ && persistedCorrection.text.length > 0
117
+ ? persistedCorrection
118
+ : undefined;
108
119
  const evidenceCount = countSubmittedEvidence(diagnosticJson);
109
120
  const hasProvenanceField = Object.hasOwn(record, 'provenance');
110
121
  const provenance = hasProvenanceField ? normalizePainProvenance(record.provenance) : undefined;
@@ -132,13 +143,14 @@ function validatePersistedIngressFacts(diagnosticJson) {
132
143
  provenance: deriveProvenanceFromIngressFacts(ingress.payload.origin, ingress.payload.correlation),
133
144
  evidenceCount,
134
145
  errorCode: null,
146
+ ...(correctionEvidence !== undefined ? { correctionEvidence } : {}),
135
147
  };
136
148
  }
137
149
  // Legacy payload without the v1 namespace: the persisted top-level
138
150
  // provenance is the authority. This branch fabricates nothing — it only
139
151
  // reads what the writer persisted.
140
152
  if (provenance !== undefined) {
141
- return { provenance, evidenceCount, errorCode: null };
153
+ return { provenance, evidenceCount, errorCode: null, ...(correctionEvidence !== undefined ? { correctionEvidence } : {}) };
142
154
  }
143
155
  // Legacy normalization branch (SPEC §9): no top-level provenance and no
144
156
  // painIngress block — re-derive from the payload's own persisted fields
@@ -150,7 +162,7 @@ function validatePersistedIngressFacts(diagnosticJson) {
150
162
  reason: typeof record.reasonSummary === 'string' ? record.reasonSummary : '',
151
163
  sessionId: typeof record.sessionIdHint === 'string' ? record.sessionIdHint : undefined,
152
164
  };
153
- return { provenance: inferProvenance(recordData), evidenceCount, errorCode: null };
165
+ return { provenance: inferProvenance(recordData), evidenceCount, errorCode: null, ...(correctionEvidence !== undefined ? { correctionEvidence } : {}) };
154
166
  }
155
167
  function provenanceReason(provenance) {
156
168
  switch (provenance) {
@@ -243,6 +255,184 @@ export async function persistPainDiagnosis(deps, opts) {
243
255
  });
244
256
  }
245
257
  }
258
+ /** Resolve the persisted diagnosis material that actually minted one candidate. */
259
+ async function resolveCandidateRunOutput(stateManager, candidate) {
260
+ const refuse = (reason) => ({ ok: false, reason: `candidate_round_unresolved:${candidate.candidateId}:${reason}` });
261
+ const sourceRunId = typeof candidate.sourceRunId === 'string' ? candidate.sourceRunId : '';
262
+ if (sourceRunId.length === 0)
263
+ return refuse('missing_source_run_id');
264
+ let run;
265
+ try {
266
+ run = await stateManager.getRun(sourceRunId);
267
+ }
268
+ catch {
269
+ return refuse('source_run_lookup_failed');
270
+ }
271
+ if (run === null || run === undefined)
272
+ return refuse('source_run_missing');
273
+ if (run.taskId !== candidate.taskId)
274
+ return refuse('source_run_task_mismatch');
275
+ if (run.executionStatus !== 'succeeded')
276
+ return refuse('source_run_not_succeeded');
277
+ const artifactId = typeof candidate.artifactId === 'string' ? candidate.artifactId : '';
278
+ if (artifactId.length > 0) {
279
+ let artifact;
280
+ try {
281
+ artifact = await stateManager.getArtifact(artifactId);
282
+ }
283
+ catch {
284
+ return refuse('artifact_lookup_failed');
285
+ }
286
+ // Some legacy candidates may have no artifact row. A present row must
287
+ // identify the same task and run as the candidate's source.
288
+ if (artifact && (artifact.runId !== sourceRunId || artifact.taskId !== candidate.taskId)) {
289
+ return refuse('artifact_run_mismatch');
290
+ }
291
+ }
292
+ let output;
293
+ if (typeof run.outputPayload === 'string' && run.outputPayload.length > 0) {
294
+ try {
295
+ const parsed = JSON.parse(run.outputPayload);
296
+ if (Value.Check(DiagnosticianOutputV1Schema, parsed))
297
+ output = parsed;
298
+ }
299
+ catch {
300
+ // Refuse this candidate; never substitute a different run's output.
301
+ }
302
+ }
303
+ if (output === undefined)
304
+ return refuse('owning_output_unparseable');
305
+ return { ok: true, output, sourceRunId };
306
+ }
307
+ /**
308
+ * SPEC-CODEX-PAIN-CLOSURE-001 CR-03 (§7.2): full-gate re-evaluation from
309
+ * PERSISTED materials only (task diagnosticJson + candidate's source run), shared
310
+ * by the CLI mutation paths so their write permission can never contradict
311
+ * the production gate on the same material. The evaluation is PERSISTED as a
312
+ * new admission version (append-only; idempotent by material fingerprint).
313
+ */
314
+ export async function evaluateAndPersistAdmissionFromStore(stateManager, candidateIds, input) {
315
+ // SPEC review round-4 P1: resolve the REAL diagnosis lineage. A split-pipeline
316
+ // candidate hangs off the diag_router SUBTASK; the PARENT diagnostician task
317
+ // holds the original pain input (feedback + Owner statement + evidence) and
318
+ // the router subtask's own succeeded runs hold the diagnostic output. A flat
319
+ // pipeline keeps task === ingress === output source. The parent–child relation
320
+ // is validated (subtask.inputRef === parent.taskId, parent kind/status) — a
321
+ // mismatch refuses with a lineage reason instead of re-typing materials.
322
+ const task = await stateManager.getTask(input.taskId);
323
+ if (task === null)
324
+ return { ok: false, reason: 'task_missing', nextAction: 'the diagnosis task no longer exists; re-run the diagnosis' };
325
+ // Persisted rows always carry task_kind (NOT NULL DDL); an undefined kind
326
+ // can only come from a partial test-double fixture and follows the flat path.
327
+ const isSplitSubtask = typeof task.taskKind === 'string' && task.taskKind.startsWith('diag_');
328
+ let ingressTask = task;
329
+ if (isSplitSubtask) {
330
+ const parentRef = typeof task.inputRef === 'string' ? task.inputRef : '';
331
+ const parent = parentRef.length > 0 ? await stateManager.getTask(parentRef) : null;
332
+ if (parent === null || parent.taskKind !== 'diagnostician' || parentRef !== task.inputRef || parent.status !== 'succeeded') {
333
+ return { ok: false, reason: 'diagnosis_lineage_inconsistent', nextAction: 'the router subtask does not resolve to a succeeded diagnostician parent; re-run the diagnosis before intake' };
334
+ }
335
+ ingressTask = parent;
336
+ }
337
+ else if (typeof task.taskKind === 'string' && task.taskKind !== 'diagnostician') {
338
+ return { ok: false, reason: 'diagnosis_lineage_inconsistent', nextAction: `candidate task kind '${task.taskKind}' is not a diagnostician task or split stage; re-run the diagnosis` };
339
+ }
340
+ const reentry = validatePersistedIngressFacts(ingressTask.diagnosticJson);
341
+ if (reentry.errorCode !== null) {
342
+ return { ok: false, reason: reentry.errorCode, nextAction: 'the persisted diagnosis payload is unusable; re-run the diagnosis before intake' };
343
+ }
344
+ const ownerStatementPresent = isOwnerStatementEvidence(reentry.correctionEvidence);
345
+ const candidates = (await stateManager.getCandidatesByTaskId(ingressTask.taskId))
346
+ .filter((candidate) => candidateIds.includes(candidate.candidateId))
347
+ .filter((candidate) => candidate.taskId === input.taskId || candidate.taskId === ingressTask.taskId);
348
+ if (candidates.length !== candidateIds.length) {
349
+ return { ok: false, reason: 'diagnosis_lineage_inconsistent', nextAction: 'requested candidates do not resolve through the validated diagnosis lineage; re-run the diagnosis' };
350
+ }
351
+ /**
352
+ * SPEC review round-4 P1 (round binding): the diagnostic output is resolved
353
+ * PER CANDIDATE from that candidate's OWN `source_run_id`, cross-checked
354
+ * against its `artifact_id` + `task_id`. The previous shape picked the first
355
+ * parseable run by reverse task order and evaluated EVERY candidate against
356
+ * that one shared output, so a later round (r2) silently rewrote an earlier
357
+ * round's (r1) admission basis — confidence, evidence count and material
358
+ * fingerprint all came from the wrong diagnosis.
359
+ *
360
+ * Fail-closed (rc-3/rc-9): a missing run row, a non-succeeded run, a
361
+ * run/task mismatch, an artifact that belongs to a different run, or an
362
+ * unparseable owning output refuses the candidate. It NEVER falls back to
363
+ * another historical successful run of the same task — substituting a
364
+ * different round's material is the exact defect being closed.
365
+ */
366
+ // Resolve EVERY candidate's own round BEFORE any evaluation, so a single
367
+ // unresolved round refuses the whole call with zero writes instead of
368
+ // partially persisting a mixed-round admission set.
369
+ const resolvedRounds = new Map();
370
+ for (const candidate of candidates) {
371
+ const resolved = await resolveCandidateRunOutput(stateManager, candidate);
372
+ if (!resolved.ok) {
373
+ return {
374
+ ok: false,
375
+ reason: 'candidate_diagnosis_round_unresolved',
376
+ nextAction: `candidate ${candidate.candidateId} does not resolve to its own diagnosis run output (${resolved.reason}); re-run the diagnosis for this round before intake`,
377
+ };
378
+ }
379
+ resolvedRounds.set(candidate.candidateId, resolved);
380
+ }
381
+ const decisions = new Map();
382
+ const evaluatedAt = new Date().toISOString();
383
+ for (const candidate of candidates) {
384
+ const round = resolvedRounds.get(candidate.candidateId);
385
+ if (round === undefined)
386
+ continue;
387
+ const { output } = round;
388
+ // The fingerprint is computed from THIS candidate's own material, so two
389
+ // rounds never collide on the same idempotency key.
390
+ const materialFingerprint = fingerprintMaterial([
391
+ JSON.stringify(output),
392
+ reentry.provenance,
393
+ String(reentry.evidenceCount),
394
+ ownerStatementPresent && reentry.correctionEvidence ? fingerprintStatement(reentry.correctionEvidence.text) : 'no-owner-statement',
395
+ round.sourceRunId,
396
+ candidate.candidateId,
397
+ ]);
398
+ const [admission] = evaluateCandidateAdmissions([candidate], output, {
399
+ provenance: reentry.provenance,
400
+ inputEvidenceCount: reentry.evidenceCount,
401
+ ...(ownerStatementPresent ? { ownerStatementPresent } : {}),
402
+ });
403
+ if (admission === undefined)
404
+ continue;
405
+ decisions.set(candidate.candidateId, {
406
+ decision: admission.admission.decision,
407
+ reason: admission.admission.reason,
408
+ nextAction: admission.admission.nextAction,
409
+ });
410
+ const persisted = await stateManager.appendAdmissionEvaluation(candidate.candidateId, {
411
+ evaluationId: `ae_${fingerprintMaterial([candidate.candidateId, materialFingerprint, ADMISSION_POLICY_VERSION]).slice(0, 16)}`,
412
+ decision: admission.admission.decision,
413
+ reason: admission.admission.reason,
414
+ nextAction: admission.admission.nextAction,
415
+ evaluatedAt,
416
+ policyVersion: ADMISSION_POLICY_VERSION,
417
+ provenance: reentry.provenance,
418
+ inputEvidenceCount: reentry.evidenceCount,
419
+ ownerStatementPresent,
420
+ recommendationKind: candidate.rawRecommendationKind,
421
+ confidence: output.confidence,
422
+ materialFingerprint,
423
+ materialSummary: {
424
+ inputEvidenceCount: reentry.evidenceCount,
425
+ outputEvidenceCount: output.evidence.length,
426
+ ownerStatementPresent,
427
+ ...(ownerStatementPresent && reentry.correctionEvidence ? { statementFingerprint: fingerprintStatement(reentry.correctionEvidence.text) } : {}),
428
+ },
429
+ });
430
+ if (persisted.status === 'refused') {
431
+ return { ok: false, reason: `admission_persistence_failed:${persisted.reason}`, nextAction: 'inspect state.db principle_candidates; the evaluation was not saved and intake is blocked' };
432
+ }
433
+ }
434
+ return { ok: true, decisions };
435
+ }
246
436
  export class PainSignalBridge {
247
437
  stateManager;
248
438
  runner;
@@ -293,6 +483,132 @@ export class PainSignalBridge {
293
483
  * host process) may skip this; per-cycle workers MUST dispose to avoid
294
484
  * pinning the workspace SQLite files.
295
485
  */
486
+ /**
487
+ * SPEC-CODEX-PAIN-CLOSURE-001 CR-05 (SPEC §9.1): continue the post-processing
488
+ * of a SUCCEEDED diagnosis whose candidates never finished admission /
489
+ * intake / seeding (crash between router `markTaskSucceeded` and this
490
+ * bridge's post-processing, intake/seed exceptions, consumed-but-unseeded
491
+ * orphans, pre-CR-03 rows with no persisted disposition).
492
+ *
493
+ * Hard guarantees:
494
+ * - NEVER re-runs the diagnosis LLM (materials come from the persisted
495
+ * run output + diagnosticJson);
496
+ * - idempotent (Ledger writes dedup via existsForCandidate, seeds dedup
497
+ * via findExistingDreamerTask, admission appends dedup by material
498
+ * fingerprint);
499
+ * - bounded (candidates whose processing state is terminal — complete /
500
+ * awaiting_owner / exhausted — are skipped and reported, not rescanned);
501
+ * - read/CLI paths never call this; only the worker continuation does.
502
+ */
503
+ async resumeSucceededDiagnosisPostProcessing(input) {
504
+ const task = await this.stateManager.getTask(input.taskId);
505
+ if (task === null)
506
+ return { status: 'not_applicable', reason: 'task_missing' };
507
+ if (task.status !== 'succeeded')
508
+ return { status: 'not_applicable', reason: `task_not_succeeded:${task.status}` };
509
+ const candidates = await this.stateManager.getCandidatesByTaskId(input.taskId);
510
+ if (candidates.length === 0)
511
+ return { status: 'not_applicable', reason: 'no_candidates' };
512
+ // Reconstruct the persisted diagnosis materials (output + ingress facts +
513
+ // correction evidence) — exactly what a fresh post-processing round sees.
514
+ const reentry = validatePersistedIngressFacts(task.diagnosticJson);
515
+ if (reentry.errorCode !== null) {
516
+ // SPEC §9.2 (review round-3 P1):确定无效材料不得每周期反复占预算。每个
517
+ // 候选持久化一条 needs_evidence 处置(材料不可解析)+ exhausted 终态,
518
+ // backlog 不再返回;原因与恢复入口(修复诊断)可查、可恢复。
519
+ const blockedAt = new Date().toISOString();
520
+ for (const candidate of candidates) {
521
+ try {
522
+ await this.stateManager.appendAdmissionEvaluation(candidate.candidateId, {
523
+ evaluationId: `ae_blocked_${fingerprintMaterial([candidate.candidateId, reentry.errorCode]).slice(0, 16)}`,
524
+ decision: 'needs_evidence',
525
+ reason: `diagnostic_material_unusable:${reentry.errorCode}`,
526
+ nextAction: 'repair or re-run the diagnosis via the supported CLI command; the candidate resumes automatically afterwards',
527
+ evaluatedAt: blockedAt,
528
+ policyVersion: ADMISSION_POLICY_VERSION,
529
+ provenance: 'automatic_hook',
530
+ inputEvidenceCount: 0,
531
+ ownerStatementPresent: false,
532
+ recommendationKind: candidate.rawRecommendationKind,
533
+ confidence: null,
534
+ materialFingerprint: fingerprintMaterial(['unusable', reentry.errorCode]),
535
+ materialSummary: { inputEvidenceCount: 0, outputEvidenceCount: 0, ownerStatementPresent: false },
536
+ });
537
+ await this.stateManager.updateAdmissionProcessing(candidate.candidateId, {
538
+ state: 'exhausted',
539
+ attempts: MAX_POST_PROCESSING_ATTEMPTS,
540
+ blockedReason: `diagnostic_material_unusable:${reentry.errorCode}`,
541
+ });
542
+ }
543
+ catch {
544
+ // persistence of the terminal state itself failed → stay scannable
545
+ // (rc-9: the next cycle retries this bookkeeping)
546
+ }
547
+ }
548
+ return { status: 'blocked', reason: reentry.errorCode, nextAction: 'the persisted diagnosis payload is unusable; repair or re-run the diagnosis via the supported CLI command (candidates recorded terminal manual_action_required states)' };
549
+ }
550
+ const runs = await this.stateManager.getRunsByTask(input.taskId);
551
+ let output;
552
+ const parseV1 = (payload) => {
553
+ if (typeof payload !== 'string' || payload.length === 0)
554
+ return undefined;
555
+ try {
556
+ const parsedOutput = JSON.parse(payload);
557
+ return Value.Check(DiagnosticianOutputV1Schema, parsedOutput) ? parsedOutput : undefined;
558
+ }
559
+ catch {
560
+ return undefined;
561
+ }
562
+ };
563
+ // SPEC §9.1 (review P1): the split pipeline commits candidates from the
564
+ // diag_router SUBTASK — the router's latest run output is the authoritative
565
+ // diagnostician output for continuation. The parent task's own run output
566
+ // is only a fallback for legacy flat pipelines (never a cache bypass: both
567
+ // sources are validated DiagnosticianOutputV1 persisted by the real run).
568
+ const routerRuns = await this.stateManager.getRunsByTask(`diag_router-${input.taskId}`);
569
+ for (let i = routerRuns.length - 1; i >= 0 && output === undefined; i -= 1) {
570
+ output = parseV1(routerRuns[i]?.outputPayload);
571
+ }
572
+ if (output === undefined) {
573
+ const latestRun = runs.at(-1);
574
+ output = parseV1(latestRun?.outputPayload);
575
+ }
576
+ // Skip candidates whose processing domain says the work is finished or
577
+ // blocked-with-budget-spent; they stay visible in `skipped`.
578
+ const skipped = [];
579
+ const eligible = [];
580
+ for (const candidate of candidates) {
581
+ const admission = await this.stateManager.getAdmissionDocument(candidate.candidateId);
582
+ if (!admission.ok) {
583
+ skipped.push({ candidateId: candidate.candidateId, reason: `admission_document_unreadable:${admission.reason}` });
584
+ continue;
585
+ }
586
+ const processingState = admission.document?.processing?.state;
587
+ if (processingState === 'complete' || processingState === 'awaiting_owner' || processingState === 'exhausted') {
588
+ skipped.push({ candidateId: candidate.candidateId, reason: `processing_${processingState}` });
589
+ continue;
590
+ }
591
+ eligible.push(candidate);
592
+ }
593
+ if (eligible.length === 0) {
594
+ return { status: 'not_applicable', reason: 'no_eligible_candidates', skipped, remainingCount: 0 };
595
+ }
596
+ const result = await this.onDiagnosisComplete({
597
+ taskId: input.taskId,
598
+ diagnosticianOutput: output,
599
+ painId: typeof task.inputRef === 'string' && task.inputRef.length > 0 ? task.inputRef : `diagnosis-source:${input.taskId}`,
600
+ provenance: reentry.provenance,
601
+ inputEvidenceCount: reentry.evidenceCount,
602
+ ...(reentry.correctionEvidence !== undefined ? { correctionEvidence: reentry.correctionEvidence } : {}),
603
+ });
604
+ return {
605
+ status: 'resumed',
606
+ result,
607
+ processedCandidateIds: eligible.map((c) => c.candidateId),
608
+ skipped,
609
+ remainingCount: 0,
610
+ };
611
+ }
296
612
  async dispose() {
297
613
  for (const resource of this.ownedResources) {
298
614
  try {
@@ -366,7 +682,8 @@ export class PainSignalBridge {
366
682
  // and source is not owner-initiated (manual/pain/skill:pain).
367
683
  // Zero side effects: no task creation, no runner call, no ledger writes.
368
684
  // IMPORTANT: Only short-circuit when there is NO existing task — idempotency must work.
369
- if (!existingTask && shouldShortCircuitEmptyEvidence(data.evidence?.length ?? 0, data.source)) {
685
+ if (!existingTask && !isOwnerStatementEvidence(data.correctionEvidence)
686
+ && shouldShortCircuitEmptyEvidence(data.evidence?.length ?? 0, data.source)) {
370
687
  return {
371
688
  status: 'skipped',
372
689
  painId,
@@ -440,6 +757,7 @@ export class PainSignalBridge {
440
757
  painId,
441
758
  provenance,
442
759
  inputEvidenceCount: data.evidence?.length ?? 0,
760
+ ...(data.correctionEvidence !== undefined ? { correctionEvidence: data.correctionEvidence } : {}),
443
761
  });
444
762
  }
445
763
  /**
@@ -535,8 +853,57 @@ export class PainSignalBridge {
535
853
  painId,
536
854
  provenance: reentry.provenance,
537
855
  inputEvidenceCount: reentry.evidenceCount,
856
+ ...(reentry.correctionEvidence !== undefined ? { correctionEvidence: reentry.correctionEvidence } : {}),
538
857
  });
539
858
  }
859
+ /** Fire-and-forget processing-domain update that must never break the loop (sync-safe). */
860
+ safeUpdateAdmissionProcessing(candidateId, processing) {
861
+ try {
862
+ void this.stateManager.updateAdmissionProcessing(candidateId, processing).catch(() => undefined);
863
+ }
864
+ catch {
865
+ // partial state-manager seams (test doubles) may not implement the
866
+ // processing domain; the primary outcome path stays authoritative
867
+ }
868
+ }
869
+ /**
870
+ * SPEC-CODEX-PAIN-CLOSURE-001 CR-05 (§9.2): bounded post-processing
871
+ * failure bookkeeping in the independent processing domain. Default budget
872
+ * is 3 automatic continuation attempts; exhaustion is a saved, readable
873
+ * terminal state (manual_action_required), never a silent loop. The
874
+ * diagnosis task's own attemptCount is never consumed by this.
875
+ */
876
+ async recordPostProcessingFailure(candidateId, rawReason) {
877
+ try {
878
+ const read = await this.stateManager.getAdmissionDocument(candidateId);
879
+ if (!read.ok)
880
+ return;
881
+ const current = read.document?.processing;
882
+ const attempts = (current?.attempts ?? 0) + 1;
883
+ const nowIso = new Date().toISOString();
884
+ const reason = rawReason.slice(0, 300);
885
+ const processing = attempts >= MAX_POST_PROCESSING_ATTEMPTS
886
+ ? {
887
+ state: 'exhausted',
888
+ attempts,
889
+ lastAttemptAt: nowIso,
890
+ lastFailure: { reason, nextAction: 'manual_action_required: post-processing retry budget exhausted; inspect state.db principle_candidates and telemetry, then use the supported recovery command', at: nowIso },
891
+ blockedReason: `post_processing_attempts_exhausted:${rawReason.slice(0, 120)}`,
892
+ }
893
+ : {
894
+ state: 'in_progress',
895
+ attempts,
896
+ lastAttemptAt: nowIso,
897
+ lastFailure: { reason, nextAction: 'the workspace worker continuation retries this candidate automatically', at: nowIso },
898
+ nextRetryAt: new Date(Date.now() + Math.min(60_000 * 2 ** attempts, 15 * 60_000)).toISOString(),
899
+ };
900
+ await this.stateManager.updateAdmissionProcessing(candidateId, processing);
901
+ }
902
+ catch {
903
+ // telemetry for the failure itself was already emitted by the caller;
904
+ // this bookkeeping must never mask the original processing result.
905
+ }
906
+ }
540
907
  /**
541
908
  * PRI-372 (T-G): Post-diagnosis processing extracted from onPainDetected().
542
909
  * Handles admission → intake → seedDreamer after a successful diagnosis.
@@ -544,7 +911,9 @@ export class PainSignalBridge {
544
911
  * NOT call it (INF-9, PRI-857): the router only commits stage output.
545
912
  */
546
913
  async onDiagnosisComplete(opts) {
547
- const { taskId, diagnosticianOutput, painId, provenance, inputEvidenceCount = 0 } = opts;
914
+ const { taskId, diagnosticianOutput, painId, provenance, inputEvidenceCount = 0, correctionEvidence } = opts;
915
+ const ownerStatementPresent = isOwnerStatementEvidence(correctionEvidence);
916
+ const statementFingerprint = ownerStatementPresent && correctionEvidence ? fingerprintStatement(correctionEvidence.text) : undefined;
548
917
  const candidates = await this.stateManager.getCandidatesByTaskId(taskId);
549
918
  const ledgerEntryIds = [];
550
919
  if (this.diagnosisPersistenceEnabled && diagnosticianOutput) {
@@ -555,18 +924,110 @@ export class PainSignalBridge {
555
924
  artifactId: candidates[0]?.artifactId ?? null,
556
925
  });
557
926
  }
558
- const admissionResults = diagnosticianOutput
559
- ? evaluateCandidateAdmissions(candidates, diagnosticianOutput, { provenance, inputEvidenceCount })
560
- : candidates.map((c) => ({
561
- candidateId: c.candidateId,
562
- recommendationKind: c.recommendationKind,
563
- admission: {
564
- decision: 'needs_evidence',
565
- reason: 'diagnostician_output_unavailable',
566
- nextAction: 're_run_diagnosis_or_manual_review',
567
- evidenceStatus: provenance,
568
- },
569
- }));
927
+ // Fresh completion and worker resume share the same per-candidate round
928
+ // resolver as CLI writes. Never apply the in-memory/latest task output to
929
+ // candidates minted by other runs of that task.
930
+ const candidateRounds = new Map();
931
+ const admissionResults = [];
932
+ for (const candidate of candidates) {
933
+ const resolved = await resolveCandidateRunOutput(this.stateManager, candidate);
934
+ if (!resolved.ok) {
935
+ admissionResults.push({
936
+ candidateId: candidate.candidateId,
937
+ recommendationKind: candidate.recommendationKind,
938
+ admission: {
939
+ decision: 'needs_evidence',
940
+ reason: resolved.reason,
941
+ nextAction: 're-run the diagnosis that produced this candidate or send it for manual review',
942
+ evidenceStatus: provenance,
943
+ },
944
+ });
945
+ continue;
946
+ }
947
+ candidateRounds.set(candidate.candidateId, resolved);
948
+ const [admission] = evaluateCandidateAdmissions([candidate], resolved.output, {
949
+ provenance,
950
+ inputEvidenceCount,
951
+ ...(ownerStatementPresent ? { ownerStatementPresent } : {}),
952
+ });
953
+ if (admission !== undefined)
954
+ admissionResults.push(admission);
955
+ }
956
+ // SPEC-CODEX-PAIN-CLOSURE-001 CR-03 (PRI-958): persist every full-gate
957
+ // disposition on the candidate row BEFORE any intake/seed/consume write.
958
+ // A persistence failure must block this candidate's governance writes for
959
+ // the round (SPEC §7.1) — the diagnosis-success fact itself is untouched.
960
+ const admissionPersistBlocked = new Set();
961
+ if (candidates.length > 0) {
962
+ const evaluatedAt = new Date().toISOString();
963
+ for (let i = 0; i < candidates.length; i++) {
964
+ const candidate = candidates[i];
965
+ const admission = admissionResults[i];
966
+ if (!candidate || !admission)
967
+ continue;
968
+ const round = candidateRounds.get(candidate.candidateId);
969
+ const materialFingerprint = round
970
+ ? fingerprintMaterial([
971
+ JSON.stringify(round.output),
972
+ provenance,
973
+ String(inputEvidenceCount),
974
+ statementFingerprint ?? 'no-owner-statement',
975
+ round.sourceRunId,
976
+ candidate.candidateId,
977
+ ])
978
+ : fingerprintMaterial([
979
+ 'candidate-round-unresolved',
980
+ admission.admission.reason,
981
+ provenance,
982
+ String(inputEvidenceCount),
983
+ candidate.candidateId,
984
+ ]);
985
+ const evaluation = {
986
+ evaluationId: `ae_${fingerprintMaterial([candidate.candidateId, materialFingerprint, ADMISSION_POLICY_VERSION]).slice(0, 16)}`,
987
+ decision: admission.admission.decision,
988
+ reason: admission.admission.reason,
989
+ nextAction: admission.admission.nextAction,
990
+ evaluatedAt,
991
+ policyVersion: ADMISSION_POLICY_VERSION,
992
+ provenance,
993
+ inputEvidenceCount,
994
+ ownerStatementPresent,
995
+ recommendationKind: candidate.rawRecommendationKind,
996
+ confidence: round?.output.confidence ?? null,
997
+ materialFingerprint,
998
+ materialSummary: {
999
+ inputEvidenceCount,
1000
+ outputEvidenceCount: round?.output.evidence.length ?? 0,
1001
+ ownerStatementPresent,
1002
+ ...(statementFingerprint !== undefined ? { statementFingerprint } : {}),
1003
+ },
1004
+ };
1005
+ try {
1006
+ if (typeof this.stateManager.appendAdmissionEvaluation !== 'function') {
1007
+ throw new Error('state_manager_admission_persistence_unavailable');
1008
+ }
1009
+ const persisted = await this.stateManager.appendAdmissionEvaluation(candidate.candidateId, evaluation);
1010
+ if (persisted.status === 'refused') {
1011
+ admissionPersistBlocked.add(candidate.candidateId);
1012
+ this.eventEmitter?.emitTelemetry({
1013
+ eventType: 'admission_persistence_failed',
1014
+ traceId: candidate.candidateId,
1015
+ timestamp: evaluatedAt,
1016
+ payload: { reason: persisted.reason, nextAction: 'inspect state.db principle_candidates; the disposition was not saved and intake is blocked for this candidate this round' },
1017
+ });
1018
+ }
1019
+ }
1020
+ catch (persistErr) {
1021
+ admissionPersistBlocked.add(candidate.candidateId);
1022
+ this.eventEmitter?.emitTelemetry({
1023
+ eventType: 'admission_persistence_failed',
1024
+ traceId: candidate.candidateId,
1025
+ timestamp: evaluatedAt,
1026
+ payload: { reason: persistErr instanceof Error ? persistErr.message : String(persistErr), nextAction: 'inspect state.db principle_candidates; the disposition was not saved and intake is blocked for this candidate this round' },
1027
+ });
1028
+ }
1029
+ }
1030
+ }
570
1031
  const seedFailureCandidateIds = [];
571
1032
  const notInternalizable = [];
572
1033
  // PRI-642 §10: per-candidate ledger/seed linkage for outcome reporting.
@@ -586,12 +1047,39 @@ export class PainSignalBridge {
586
1047
  continue;
587
1048
  if (admission.admission.decision !== 'admitted') {
588
1049
  this.emitAdmissionEvent(candidate.candidateId, admission.admission);
1050
+ // CR-03: a non-admitted disposition is a saved, explainable terminal
1051
+ // state for this material — the worker must not keep rescanning it.
1052
+ this.safeUpdateAdmissionProcessing(candidate.candidateId, {
1053
+ state: 'awaiting_owner',
1054
+ attempts: 0,
1055
+ blockedReason: `${admission.admission.decision}:${admission.admission.reason}`,
1056
+ });
1057
+ continue;
1058
+ }
1059
+ // CR-03 (SPEC §7.1): the disposition itself failed to persist — no
1060
+ // Ledger write, no consume, no seed for this candidate this round.
1061
+ if (admissionPersistBlocked.has(candidate.candidateId)) {
589
1062
  continue;
590
1063
  }
1064
+ // CR-05 (SPEC §9.1): resume idempotency — when a previous round already
1065
+ // wrote this candidate's Ledger entry, skip intake (no duplicate
1066
+ // writes) and fall through to seed/link recovery below.
1067
+ // CR-05 resume idempotency (guarded for adapters whose seam returns
1068
+ // undefined instead of null): only a REAL persisted entry short-circuits.
1069
+ const existingLedgerEntry = this.ledgerAdapter.existsForCandidate(candidate.candidateId) ?? null;
1070
+ if (existingLedgerEntry !== null && typeof existingLedgerEntry.id === 'string') {
1071
+ ledgerEntryIds.push(existingLedgerEntry.id);
1072
+ ledgerEntryByCandidate.set(candidate.candidateId, existingLedgerEntry.id);
1073
+ }
591
1074
  // sibling 隔离:一个 candidate 的 intake 失败不应中止整批(ERR-089 模式)
592
1075
  let intakeResult;
593
1076
  try {
594
- intakeResult = await this.intakeService.intake(candidate.candidateId);
1077
+ if (existingLedgerEntry !== null && typeof existingLedgerEntry.id === 'string') {
1078
+ intakeResult = { outcome: 'ledger_entry', written: false, entry: existingLedgerEntry };
1079
+ }
1080
+ else {
1081
+ intakeResult = await this.intakeService.intake(candidate.candidateId);
1082
+ }
595
1083
  }
596
1084
  catch (intakeErr) {
597
1085
  this.eventEmitter?.emitTelemetry({
@@ -600,6 +1088,10 @@ export class PainSignalBridge {
600
1088
  timestamp: new Date().toISOString(),
601
1089
  payload: { reason: intakeErr instanceof Error ? intakeErr.message : String(intakeErr) },
602
1090
  });
1091
+ // CR-05 (SPEC §9.1): the succeeded diagnosis with an unfinished
1092
+ // intake is an orphan the worker continuation must pick up — record
1093
+ // the attempt in the independent processing domain.
1094
+ void this.recordPostProcessingFailure(candidate.candidateId, `intake_failed:${intakeErr instanceof Error ? intakeErr.message : String(intakeErr)}`).catch(() => undefined);
603
1095
  continue;
604
1096
  }
605
1097
  // Phase 1 / PR1 (Principle Ledger write boundary): the intake service only
@@ -643,6 +1135,13 @@ export class PainSignalBridge {
643
1135
  selectedPrincipleId: recommended?.selectedPrincipleId,
644
1136
  confidence: recommended?.confidence,
645
1137
  });
1138
+ // CR-05: waiting for the Owner's reuse decision is a saved
1139
+ // awaiting-owner state, not a technical retry.
1140
+ this.safeUpdateAdmissionProcessing(candidate.candidateId, {
1141
+ state: 'awaiting_owner',
1142
+ attempts: 0,
1143
+ blockedReason: 'reuse_pending_owner',
1144
+ });
646
1145
  continue;
647
1146
  }
648
1147
  try {
@@ -715,12 +1214,36 @@ export class PainSignalBridge {
715
1214
  timestamp: new Date().toISOString(),
716
1215
  payload: { error: String(seedErr) },
717
1216
  });
1217
+ // CR-05 (SPEC §9.1): seed failure is a post-processing orphan —
1218
+ // consumed-but-unseeded must stay discoverable by the worker
1219
+ // continuation (never hidden behind the consumed status).
1220
+ void this.recordPostProcessingFailure(candidate.candidateId, `seed_failed:${String(seedErr)}`).catch(() => undefined);
718
1221
  }
719
1222
  if (candidate.status !== 'consumed') {
720
1223
  await this.stateManager.updateCandidateStatus(candidate.candidateId, { status: 'consumed' });
721
1224
  }
722
1225
  }
723
1226
  }
1227
+ // CR-03/CR-05: mark the candidates whose post-processing reached a real
1228
+ // terminal state (seeded / ledger-written / not internalizable) complete
1229
+ // so the worker continuation stops rescanning them.
1230
+ for (let i = 0; i < candidates.length; i++) {
1231
+ const candidate = candidates[i];
1232
+ const admission = admissionResults[i];
1233
+ if (!candidate || !admission || admission.admission.decision !== 'admitted')
1234
+ continue;
1235
+ if (admissionPersistBlocked.has(candidate.candidateId))
1236
+ continue;
1237
+ if (seedFailureCandidateIds.includes(candidate.candidateId))
1238
+ continue;
1239
+ if (reuseReviewRequiredByCandidate.has(candidate.candidateId))
1240
+ continue;
1241
+ this.safeUpdateAdmissionProcessing(candidate.candidateId, {
1242
+ state: 'complete',
1243
+ attempts: 0,
1244
+ completedAt: new Date().toISOString(),
1245
+ });
1246
+ }
724
1247
  const seedFailureNote = seedFailureCandidateIds.length > 0
725
1248
  ? `dreamer_seed_failed:${seedFailureCandidateIds.join(',')}`
726
1249
  : '';