principles-disciple 1.8.0 → 1.8.2

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 (460) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +6 -1
  10. package/package.json +13 -15
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +185 -63
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +166 -139
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +263 -36
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +603 -0
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/src/service/subagent-workflow/types.ts +378 -0
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -127
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -99
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -12
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -51
  403. package/dist/hooks/progressive-trust-gate.js +0 -89
  404. package/dist/hooks/prompt.d.ts +0 -47
  405. package/dist/hooks/prompt.js +0 -884
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -567
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -52
  423. package/dist/service/empathy-observer-manager.js +0 -229
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -974
  428. package/dist/service/nocturnal-runtime.d.ts +0 -183
  429. package/dist/service/nocturnal-service.d.ts +0 -163
  430. package/dist/service/nocturnal-service.js +0 -787
  431. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  432. package/dist/service/nocturnal-target-selector.js +0 -315
  433. package/dist/service/phase3-input-filter.d.ts +0 -73
  434. package/dist/service/phase3-input-filter.js +0 -172
  435. package/dist/service/runtime-summary-service.d.ts +0 -122
  436. package/dist/service/runtime-summary-service.js +0 -485
  437. package/dist/service/trajectory-service.d.ts +0 -2
  438. package/dist/service/trajectory-service.js +0 -15
  439. package/dist/tools/critique-prompt.d.ts +0 -14
  440. package/dist/tools/deep-reflect.d.ts +0 -39
  441. package/dist/tools/deep-reflect.js +0 -350
  442. package/dist/tools/model-index.d.ts +0 -9
  443. package/dist/types/event-types.d.ts +0 -306
  444. package/dist/types/event-types.js +0 -106
  445. package/dist/types/hygiene-types.d.ts +0 -20
  446. package/dist/types/hygiene-types.js +0 -12
  447. package/dist/types/runtime-summary.d.ts +0 -47
  448. package/dist/types/runtime-summary.js +0 -1
  449. package/dist/types.d.ts +0 -50
  450. package/dist/types.js +0 -22
  451. package/dist/utils/file-lock.d.ts +0 -71
  452. package/dist/utils/file-lock.js +0 -309
  453. package/dist/utils/glob-match.d.ts +0 -28
  454. package/dist/utils/hashing.d.ts +0 -9
  455. package/dist/utils/io.d.ts +0 -6
  456. package/dist/utils/io.js +0 -106
  457. package/dist/utils/nlp.d.ts +0 -9
  458. package/dist/utils/plugin-logger.d.ts +0 -39
  459. package/dist/utils/subagent-probe.d.ts +0 -34
  460. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import {
5
+ computePainScore,
6
+ painSeverityLabel,
7
+ writePainFlag,
8
+ readPainFlagData
9
+ } from '../../src/core/pain';
10
+
11
+ vi.mock('fs');
12
+
13
+ describe('Pain Detection Module', () => {
14
+ describe('computePainScore', () => {
15
+ it('should compute score correctly', () => {
16
+ expect(computePainScore(0, false, false, 0)).toBe(0);
17
+ expect(computePainScore(1, false, false, 0)).toBe(70);
18
+ expect(computePainScore(0, true, false, 0)).toBe(40);
19
+ expect(computePainScore(0, false, true, 0)).toBe(30);
20
+ expect(computePainScore(1, true, true, 20)).toBe(100); // capped at 100
21
+ });
22
+ });
23
+
24
+ describe('painSeverityLabel', () => {
25
+ it('should return correct severity labels', () => {
26
+ expect(painSeverityLabel(0, true)).toBe('critical');
27
+ expect(painSeverityLabel(80)).toBe('high');
28
+ expect(painSeverityLabel(50)).toBe('medium');
29
+ expect(painSeverityLabel(25)).toBe('low');
30
+ expect(painSeverityLabel(10)).toBe('info');
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('PathResolver', () => {
4
+ it('should have correct exports', async () => {
5
+ const { PathResolver, PD_ENV_VARS, PD_ENV_DESCRIPTIONS } = await import('../../src/core/path-resolver.js');
6
+ expect(PathResolver).toBeDefined();
7
+ expect(PD_ENV_VARS).toBeDefined();
8
+ expect(PD_ENV_DESCRIPTIONS).toBeDefined();
9
+ });
10
+
11
+
12
+
13
+ it('normalizes workspaceDir passed via constructor', async () => {
14
+ const { PathResolver } = await import('../../src/core/path-resolver.js');
15
+ const resolver = new PathResolver({ workspaceDir: '/tmp/../tmp/workspace' });
16
+ expect(resolver.getWorkspaceDir()).toBe('/tmp/workspace');
17
+ });
18
+
19
+ it('should have all required path keys', async () => {
20
+ const { PathResolver } = await import('../../src/core/path-resolver.js');
21
+ const resolver = new PathResolver({ workspaceDir: '/test/workspace' });
22
+
23
+ const requiredKeys = ['PROFILE', 'PLAN', 'AGENT_SCORECARD', 'PAIN_FLAG', 'EVOLUTION_QUEUE', 'THINKING_OS', 'THINKING_OS_USAGE', 'THINKING_OS_CANDIDATES'];
24
+
25
+ for (const key of requiredKeys) {
26
+ expect(() => resolver.resolve(key)).not.toThrow();
27
+ }
28
+ });
29
+ });
30
+
31
+ describe('Persistence Fix - Placeholder', () => {
32
+ it('trust engine persistence is tested via workspace-context tests', () => {
33
+ expect(true).toBe(true);
34
+ });
35
+
36
+ it('pain dictionary persistence is tested via workspace-context tests', () => {
37
+ expect(true).toBe(true);
38
+ });
39
+
40
+ it('pain config save method exists', async () => {
41
+ const { PainConfig } = await import('../../src/core/config.js');
42
+ const config = new PainConfig('/test/state');
43
+ expect(typeof config.save).toBe('function');
44
+ });
45
+ });
46
+
47
+
48
+ it('should resolve extension anchors and worker path', async () => {
49
+ const { PathResolver } = await import('../../src/core/path-resolver.js');
50
+ PathResolver.setExtensionRoot('/tmp/ext-root');
51
+ const resolver = new PathResolver({ workspaceDir: '/test/workspace' });
52
+
53
+ expect(resolver.resolve('EXTENSION_ROOT')).toBe('/tmp/ext-root');
54
+ expect(resolver.resolve('EXTENSION_SRC')).toBe('/tmp/ext-root/src');
55
+ expect(resolver.resolve('EXTENSION_DIST')).toBe('/tmp/ext-root/dist');
56
+ expect(resolver.resolve('EVOLUTION_WORKER')).toMatch(/\/tmp\/ext-root\/(src|dist)\/service\/evolution-worker\.(ts|js)$/);
57
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { planStatus } from '../../src/utils/io.js';
3
+ import { resolvePdPath } from '../../src/core/paths.js';
4
+ import * as fs from 'fs';
5
+
6
+ vi.mock('fs');
7
+
8
+ describe('Path Anchoring Integration', () => {
9
+ const workspaceDir = '/mock/workspace';
10
+
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ it('should resolve PROFILE.json inside .principles/', () => {
16
+ const expected = '/mock/workspace/.principles/PROFILE.json';
17
+ expect(resolvePdPath(workspaceDir, 'PROFILE')).toBe(expected);
18
+ });
19
+
20
+ it('should resolve PLAN.md at the project root', () => {
21
+ const expected = '/mock/workspace/PLAN.md';
22
+ expect(resolvePdPath(workspaceDir, 'PLAN')).toBe(expected);
23
+ });
24
+
25
+ it('should resolve AGENT_SCORECARD.json inside .state/', () => {
26
+ const expected = '/mock/workspace/.state/AGENT_SCORECARD.json';
27
+ expect(resolvePdPath(workspaceDir, 'AGENT_SCORECARD')).toBe(expected);
28
+ });
29
+
30
+ it('planStatus should look for PLAN.md in the root', () => {
31
+ const rootPlanPath = '/mock/workspace/PLAN.md';
32
+ vi.mocked(fs.existsSync).mockImplementation((p) => p === rootPlanPath);
33
+ vi.mocked(fs.readFileSync).mockReturnValue('STATUS: READY');
34
+
35
+ const status = planStatus(workspaceDir);
36
+
37
+ expect(status).toBe('READY');
38
+ expect(fs.existsSync).toHaveBeenCalledWith(rootPlanPath);
39
+ // Verify it does NOT look in docs/
40
+ expect(fs.existsSync).not.toHaveBeenCalledWith(expect.stringContaining('docs/PLAN.md'));
41
+ });
42
+ });
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Integration Tests for Phase 7 Rollout
3
+ * =====================================
4
+ *
5
+ * Tests the complete integration flow:
6
+ * - Training → checkpoint → eval → promotion gate → deployment binding
7
+ * - Only local-reader eligible for first rollout
8
+ * - Orchestrator review required for promotion
9
+ * - Rollback path works
10
+ */
11
+
12
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import * as os from 'os';
16
+ import {
17
+ registerTrainingRun,
18
+ startTrainingRun,
19
+ completeTrainingRun,
20
+ registerCheckpoint,
21
+ attachEvalSummary,
22
+ markCheckpointDeployable,
23
+ getCheckpoint,
24
+ } from '../../src/core/model-training-registry.js';
25
+ import {
26
+ bindCheckpointToWorkerProfile,
27
+ getDeployment,
28
+ enableRoutingForProfile,
29
+ disableRoutingForProfile,
30
+ rollbackDeployment,
31
+ isRoutingEnabledForProfile,
32
+ getActiveCheckpointForProfile,
33
+ } from '../../src/core/model-deployment-registry.js';
34
+ import {
35
+ evaluatePromotionGate,
36
+ advancePromotion,
37
+ getPromotionState,
38
+ getPromotionRecord,
39
+ DEFAULT_BASELINE_METRICS,
40
+ DEFAULT_MIN_DELTA,
41
+ } from '../../src/core/promotion-gate.js';
42
+
43
+ describe('Phase 7 Rollout Integration', () => {
44
+ // -------------------------------------------------------------------------
45
+ // Test setup
46
+ // -------------------------------------------------------------------------
47
+
48
+ let tempDir: string;
49
+ let stateDir: string;
50
+
51
+ beforeEach(() => {
52
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rollout-test-'));
53
+ stateDir = path.join(tempDir, '.state', 'nocturnal');
54
+ fs.mkdirSync(stateDir, { recursive: true });
55
+ });
56
+
57
+ afterEach(() => {
58
+ try {
59
+ if (fs.existsSync(tempDir)) {
60
+ fs.rmSync(tempDir, { recursive: true, force: true });
61
+ }
62
+ } catch {
63
+ // Ignore cleanup errors
64
+ }
65
+ });
66
+
67
+ // -------------------------------------------------------------------------
68
+ // Helper: Create a deployable checkpoint with eval
69
+ // -------------------------------------------------------------------------
70
+
71
+ function createDeployableCheckpoint(params: {
72
+ delta?: number;
73
+ verdict?: 'pass' | 'fail' | 'compare_only';
74
+ candidateScore?: number;
75
+ targetModelFamily?: string;
76
+ markDeplorable?: boolean; // If false, skip markCheckpointDeployable
77
+ }): string {
78
+ const {
79
+ delta = 0.08,
80
+ verdict = 'pass',
81
+ candidateScore = 0.8,
82
+ targetModelFamily = 'qwen2.5-7b-reader',
83
+ markDeplorable = true,
84
+ } = params;
85
+
86
+ // Register training run
87
+ const run = registerTrainingRun(stateDir, {
88
+ targetModelFamily,
89
+ datasetFingerprint: 'fp-dataset-abc',
90
+ exportId: 'export-123',
91
+ sampleCount: 100,
92
+ configFingerprint: 'fp-config-xyz',
93
+ });
94
+
95
+ // Complete the training run so checkpoint can be marked deployable
96
+ startTrainingRun(stateDir, run.trainRunId);
97
+ completeTrainingRun(stateDir, run.trainRunId);
98
+
99
+ // Register checkpoint
100
+ const checkpoint = registerCheckpoint(stateDir, {
101
+ trainRunId: run.trainRunId,
102
+ targetModelFamily,
103
+ artifactPath: path.join(tempDir, 'checkpoints', 'ckpt-001'),
104
+ });
105
+
106
+ // Attach eval
107
+ const evalId = `eval-${Date.now()}`;
108
+ attachEvalSummary(stateDir, checkpoint.checkpointId, {
109
+ evalId,
110
+ checkpointId: checkpoint.checkpointId,
111
+ benchmarkId: 'benchmark-001',
112
+ targetModelFamily,
113
+ mode: 'reduced_prompt',
114
+ baselineScore: 0.7,
115
+ candidateScore,
116
+ delta,
117
+ verdict,
118
+ });
119
+
120
+ // Mark deployable (only if verdict is pass/compare_only and markDeployable is true)
121
+ if (markDeplorable && (verdict === 'pass' || verdict === 'compare_only')) {
122
+ markCheckpointDeployable(stateDir, checkpoint.checkpointId, true);
123
+ }
124
+
125
+ return checkpoint.checkpointId;
126
+ }
127
+
128
+ // -------------------------------------------------------------------------
129
+ // Helper: Advance checkpoint to shadow_ready
130
+ // -------------------------------------------------------------------------
131
+
132
+ function advanceToShadowReady(checkpointId: string): void {
133
+ advancePromotion(stateDir, {
134
+ checkpointId,
135
+ targetProfile: 'local-reader',
136
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
137
+ orchestratorReviewPassed: true,
138
+ reviewNote: 'Approved for shadow rollout',
139
+ });
140
+ }
141
+
142
+ // -------------------------------------------------------------------------
143
+ // Test: Complete rollout flow
144
+ // -------------------------------------------------------------------------
145
+
146
+ describe('Complete Rollout Flow', () => {
147
+ it('should complete: checkpoint → eval → promotion → binding → routing', () => {
148
+ // Step 1: Create deployable checkpoint
149
+ const checkpointId = createDeployableCheckpoint({
150
+ delta: 0.15,
151
+ verdict: 'pass',
152
+ candidateScore: 0.85,
153
+ });
154
+
155
+ // Step 2: Evaluate promotion gate
156
+ const gateResult = evaluatePromotionGate(stateDir, {
157
+ checkpointId,
158
+ targetProfile: 'local-reader',
159
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
160
+ });
161
+ expect(gateResult.passes).toBe(true);
162
+
163
+ // Step 3: Advance promotion state
164
+ advanceToShadowReady(checkpointId);
165
+ expect(getPromotionState(stateDir, checkpointId)).toBe('shadow_ready');
166
+
167
+ // Step 4: Bind checkpoint to worker profile
168
+ const deployment = bindCheckpointToWorkerProfile(
169
+ stateDir,
170
+ 'local-reader',
171
+ checkpointId,
172
+ 'Initial shadow deployment'
173
+ );
174
+ expect(deployment.activeCheckpointId).toBe(checkpointId);
175
+ expect(deployment.routingEnabled).toBe(false);
176
+
177
+ // Step 5: Verify routing is disabled initially
178
+ expect(isRoutingEnabledForProfile(stateDir, 'local-reader')).toBe(false);
179
+
180
+ // Step 6: Enable routing
181
+ const updated = enableRoutingForProfile(stateDir, 'local-reader');
182
+ expect(updated.routingEnabled).toBe(true);
183
+
184
+ // Step 7: Verify routing is enabled
185
+ expect(isRoutingEnabledForProfile(stateDir, 'local-reader')).toBe(true);
186
+
187
+ // Step 8: Disable routing
188
+ disableRoutingForProfile(stateDir, 'local-reader');
189
+ expect(isRoutingEnabledForProfile(stateDir, 'local-reader')).toBe(false);
190
+ });
191
+ });
192
+
193
+ // -------------------------------------------------------------------------
194
+ // Test: Only local-reader eligible for first rollout
195
+ // -------------------------------------------------------------------------
196
+
197
+ describe('First Rollout Profile Constraint', () => {
198
+ it('should allow local-reader for first rollout', () => {
199
+ const checkpointId = createDeployableCheckpoint({
200
+ delta: 0.15,
201
+ verdict: 'pass',
202
+ });
203
+
204
+ advanceToShadowReady(checkpointId);
205
+
206
+ // Should succeed
207
+ const deployment = bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpointId);
208
+ expect(deployment.workerProfile).toBe('local-reader');
209
+ });
210
+
211
+ it('should allow deployment binding without promotion gate check (backward compat)', () => {
212
+ // This tests the case where a checkpoint is already bound
213
+ // and the promotion gate check is added later
214
+ // First, create a checkpoint WITHOUT promotion gate
215
+ const run = registerTrainingRun(stateDir, {
216
+ targetModelFamily: 'qwen2.5-7b-reader',
217
+ datasetFingerprint: 'fp-dataset-abc',
218
+ exportId: 'export-123',
219
+ sampleCount: 100,
220
+ configFingerprint: 'fp-config-xyz',
221
+ });
222
+ startTrainingRun(stateDir, run.trainRunId);
223
+ completeTrainingRun(stateDir, run.trainRunId);
224
+
225
+ const checkpoint = registerCheckpoint(stateDir, {
226
+ trainRunId: run.trainRunId,
227
+ targetModelFamily: 'qwen2.5-7b-reader',
228
+ artifactPath: path.join(tempDir, 'checkpoints', 'ckpt-no-promotion'),
229
+ });
230
+
231
+ // Attach eval but DON'T mark deployable (simulating old state before promotion)
232
+ attachEvalSummary(stateDir, checkpoint.checkpointId, {
233
+ evalId: 'eval-no-promotion',
234
+ checkpointId: checkpoint.checkpointId,
235
+ benchmarkId: 'benchmark-001',
236
+ targetModelFamily: 'qwen2.5-7b-reader',
237
+ mode: 'reduced_prompt',
238
+ baselineScore: 0.7,
239
+ candidateScore: 0.8,
240
+ delta: 0.1,
241
+ verdict: 'pass',
242
+ });
243
+
244
+ // Without promotion gate evaluated, binding should fail
245
+ expect(() => {
246
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint.checkpointId);
247
+ }).toThrow(/not deployable/);
248
+ });
249
+ });
250
+
251
+ // -------------------------------------------------------------------------
252
+ // Test: Orchestrator review required
253
+ // -------------------------------------------------------------------------
254
+
255
+ describe('Orchestrator Review Requirement', () => {
256
+ it('should not reach shadow_ready without orchestrator review', () => {
257
+ const checkpointId = createDeployableCheckpoint({
258
+ delta: 0.15,
259
+ verdict: 'pass',
260
+ candidateScore: 0.85,
261
+ });
262
+
263
+ // Advance without orchestrator review
264
+ const promotion = advancePromotion(stateDir, {
265
+ checkpointId,
266
+ targetProfile: 'local-reader',
267
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
268
+ orchestratorReviewPassed: false,
269
+ });
270
+
271
+ // Should be candidate_only, not shadow_ready
272
+ expect(promotion.state).toBe('candidate_only');
273
+ expect(promotion.orchestratorReviewPassed).toBe(false);
274
+ });
275
+
276
+ it('should reach shadow_ready with orchestrator review', () => {
277
+ const checkpointId = createDeployableCheckpoint({
278
+ delta: 0.15,
279
+ verdict: 'pass',
280
+ candidateScore: 0.85,
281
+ });
282
+
283
+ // Advance with orchestrator review
284
+ const promotion = advancePromotion(stateDir, {
285
+ checkpointId,
286
+ targetProfile: 'local-reader',
287
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
288
+ orchestratorReviewPassed: true,
289
+ reviewNote: 'Looks good, proceed',
290
+ });
291
+
292
+ expect(promotion.state).toBe('shadow_ready');
293
+ expect(promotion.orchestratorReviewPassed).toBe(true);
294
+ expect(promotion.reviewNote).toBe('Looks good, proceed');
295
+ });
296
+ });
297
+
298
+ // -------------------------------------------------------------------------
299
+ // Test: Rollback path
300
+ // -------------------------------------------------------------------------
301
+
302
+ describe('Rollback Path', () => {
303
+ it('should preserve previous checkpoint on re-binding', () => {
304
+ // Create first checkpoint
305
+ const checkpoint1 = createDeployableCheckpoint({
306
+ delta: 0.1,
307
+ verdict: 'pass',
308
+ targetModelFamily: 'qwen2.5-7b-reader',
309
+ });
310
+ advanceToShadowReady(checkpoint1);
311
+
312
+ // Bind first checkpoint
313
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint1);
314
+ expect(getActiveCheckpointForProfile(stateDir, 'local-reader')).toBe(checkpoint1);
315
+
316
+ // Create second checkpoint
317
+ const checkpoint2 = createDeployableCheckpoint({
318
+ delta: 0.15,
319
+ verdict: 'pass',
320
+ targetModelFamily: 'qwen2.5-7b-reader',
321
+ });
322
+ advanceToShadowReady(checkpoint2);
323
+
324
+ // Bind second checkpoint (should update, not replace chain)
325
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint2, 'Update to newer checkpoint');
326
+
327
+ const deployment = getDeployment(stateDir, 'local-reader');
328
+ expect(deployment.activeCheckpointId).toBe(checkpoint2);
329
+ expect(deployment.previousCheckpointId).toBe(checkpoint1);
330
+ });
331
+
332
+ it('should rollback to previous checkpoint', () => {
333
+ // Create and bind first checkpoint
334
+ const checkpoint1 = createDeployableCheckpoint({
335
+ delta: 0.1,
336
+ verdict: 'pass',
337
+ targetModelFamily: 'qwen2.5-7b-reader',
338
+ });
339
+ advanceToShadowReady(checkpoint1);
340
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint1);
341
+
342
+ // Create and bind second checkpoint
343
+ const checkpoint2 = createDeployableCheckpoint({
344
+ delta: 0.15,
345
+ verdict: 'pass',
346
+ targetModelFamily: 'qwen2.5-7b-reader',
347
+ });
348
+ advanceToShadowReady(checkpoint2);
349
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint2);
350
+
351
+ // Enable routing
352
+ enableRoutingForProfile(stateDir, 'local-reader');
353
+
354
+ // Rollback
355
+ const rolledBack = rollbackDeployment(stateDir, 'local-reader', 'Rollback due to instability');
356
+
357
+ // Verify rollback
358
+ expect(rolledBack.activeCheckpointId).toBe(checkpoint1);
359
+ expect(rolledBack.previousCheckpointId).toBe(checkpoint2);
360
+ expect(rolledBack.routingEnabled).toBe(false); // Routing disabled after rollback
361
+
362
+ // Verify routing is disabled
363
+ expect(isRoutingEnabledForProfile(stateDir, 'local-reader')).toBe(false);
364
+ });
365
+
366
+ it('should fail rollback when no previous checkpoint exists', () => {
367
+ // Single checkpoint with no previous - rollback should fail
368
+ const checkpointId = createDeployableCheckpoint({
369
+ delta: 0.08,
370
+ verdict: 'pass',
371
+ targetModelFamily: 'qwen2.5-7b-reader',
372
+ });
373
+ advanceToShadowReady(checkpointId);
374
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpointId);
375
+
376
+ // Rollback should fail because there's no previous checkpoint
377
+ expect(() => {
378
+ rollbackDeployment(stateDir, 'local-reader');
379
+ }).toThrow(/no previous checkpoint/);
380
+ });
381
+ });
382
+
383
+ // -------------------------------------------------------------------------
384
+ // Test: Fail-closed on various scenarios
385
+ // -------------------------------------------------------------------------
386
+
387
+ describe('Fail-Closed Behavior', () => {
388
+ it('should fail binding if checkpoint has no eval', () => {
389
+ const run = registerTrainingRun(stateDir, {
390
+ targetModelFamily: 'qwen2.5-7b-reader',
391
+ datasetFingerprint: 'fp-dataset-abc',
392
+ exportId: 'export-123',
393
+ sampleCount: 100,
394
+ configFingerprint: 'fp-config-xyz',
395
+ });
396
+
397
+ const checkpoint = registerCheckpoint(stateDir, {
398
+ trainRunId: run.trainRunId,
399
+ targetModelFamily: 'qwen2.5-7b-reader',
400
+ artifactPath: path.join(tempDir, 'checkpoints', 'ckpt-no-eval'),
401
+ });
402
+
403
+ // Should fail because checkpoint is not deployable (no eval)
404
+ expect(() => {
405
+ bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint.checkpointId);
406
+ }).toThrow(/not deployable/);
407
+ });
408
+
409
+ it('should fail binding if eval verdict is fail', () => {
410
+ const checkpointId = createDeployableCheckpoint({
411
+ delta: -0.1,
412
+ verdict: 'fail',
413
+ candidateScore: 0.3,
414
+ });
415
+
416
+ advancePromotion(stateDir, {
417
+ checkpointId,
418
+ targetProfile: 'local-reader',
419
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
420
+ orchestratorReviewPassed: true,
421
+ });
422
+
423
+ // Should fail because promotion state is rejected
424
+ expect(getPromotionState(stateDir, checkpointId)).toBe('rejected');
425
+ });
426
+ });
427
+
428
+ // -------------------------------------------------------------------------
429
+ // Test: Promotion state machine
430
+ // -------------------------------------------------------------------------
431
+
432
+ describe('Promotion State Machine', () => {
433
+ it('should flow: candidate_only → shadow_ready → promotable', () => {
434
+ const checkpointId = createDeployableCheckpoint({
435
+ delta: 0.1,
436
+ verdict: 'pass',
437
+ candidateScore: 0.8,
438
+ });
439
+
440
+ // Initial evaluation: should be candidate_only (no review)
441
+ let promotion = advancePromotion(stateDir, {
442
+ checkpointId,
443
+ targetProfile: 'local-reader',
444
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
445
+ orchestratorReviewPassed: false,
446
+ });
447
+ expect(promotion.state).toBe('candidate_only');
448
+
449
+ // After orchestrator review: should be shadow_ready
450
+ promotion = advancePromotion(stateDir, {
451
+ checkpointId,
452
+ targetProfile: 'local-reader',
453
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
454
+ orchestratorReviewPassed: true,
455
+ });
456
+ expect(promotion.state).toBe('shadow_ready');
457
+ expect(promotion.shadowStartedAt).toBeDefined();
458
+ });
459
+
460
+ it('should reject if delta is negative', () => {
461
+ const checkpointId = createDeployableCheckpoint({
462
+ delta: -0.05,
463
+ verdict: 'fail',
464
+ candidateScore: 0.3,
465
+ });
466
+
467
+ const promotion = advancePromotion(stateDir, {
468
+ checkpointId,
469
+ targetProfile: 'local-reader',
470
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
471
+ orchestratorReviewPassed: true,
472
+ });
473
+
474
+ expect(promotion.state).toBe('rejected');
475
+ });
476
+ });
477
+ });