principles-disciple 1.8.1 → 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 (470) 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 +4 -4
  10. package/package.json +11 -13
  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} +175 -62
  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} +160 -80
  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} +235 -79
  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/{dist/service/subagent-workflow/empathy-observer-workflow-manager.js → src/service/subagent-workflow/empathy-observer-workflow-manager.ts} +240 -117
  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/{dist/service/subagent-workflow/types.d.ts → src/service/subagent-workflow/types.ts} +137 -18
  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 -129
  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 -101
  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 -13
  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 -52
  403. package/dist/hooks/progressive-trust-gate.js +0 -134
  404. package/dist/hooks/prompt.d.ts +0 -49
  405. package/dist/hooks/prompt.js +0 -905
  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 -681
  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 -88
  423. package/dist/service/empathy-observer-manager.js +0 -414
  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 -975
  428. package/dist/service/health-query-service.d.ts +0 -170
  429. package/dist/service/health-query-service.js +0 -662
  430. package/dist/service/nocturnal-runtime.d.ts +0 -183
  431. package/dist/service/nocturnal-service.d.ts +0 -163
  432. package/dist/service/nocturnal-service.js +0 -787
  433. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  434. package/dist/service/nocturnal-target-selector.js +0 -315
  435. package/dist/service/phase3-input-filter.d.ts +0 -73
  436. package/dist/service/phase3-input-filter.js +0 -172
  437. package/dist/service/runtime-summary-service.d.ts +0 -122
  438. package/dist/service/runtime-summary-service.js +0 -485
  439. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  440. package/dist/service/subagent-workflow/index.d.ts +0 -4
  441. package/dist/service/subagent-workflow/index.js +0 -3
  442. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  443. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  444. package/dist/service/subagent-workflow/types.js +0 -11
  445. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  446. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  447. package/dist/service/trajectory-service.d.ts +0 -2
  448. package/dist/service/trajectory-service.js +0 -15
  449. package/dist/tools/critique-prompt.d.ts +0 -14
  450. package/dist/tools/deep-reflect.d.ts +0 -39
  451. package/dist/tools/deep-reflect.js +0 -350
  452. package/dist/tools/model-index.d.ts +0 -9
  453. package/dist/types/event-types.d.ts +0 -306
  454. package/dist/types/event-types.js +0 -106
  455. package/dist/types/hygiene-types.d.ts +0 -20
  456. package/dist/types/hygiene-types.js +0 -12
  457. package/dist/types/runtime-summary.d.ts +0 -47
  458. package/dist/types/runtime-summary.js +0 -1
  459. package/dist/types.d.ts +0 -50
  460. package/dist/types.js +0 -22
  461. package/dist/utils/file-lock.d.ts +0 -71
  462. package/dist/utils/file-lock.js +0 -309
  463. package/dist/utils/glob-match.d.ts +0 -28
  464. package/dist/utils/hashing.d.ts +0 -9
  465. package/dist/utils/io.d.ts +0 -6
  466. package/dist/utils/io.js +0 -106
  467. package/dist/utils/nlp.d.ts +0 -9
  468. package/dist/utils/plugin-logger.d.ts +0 -39
  469. package/dist/utils/subagent-probe.d.ts +0 -34
  470. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,845 @@
1
+ /**
2
+ * Model Deployment Registry — Tests
3
+ * ==================================
4
+ *
5
+ * Tests for worker profile → checkpoint binding, routing enable/disable,
6
+ * and rollback operations.
7
+ *
8
+ * Prerequisites: Many tests set up a training run + checkpoint in the
9
+ * training registry before exercising deployment registry operations.
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
+ bindCheckpointToWorkerProfile,
18
+ getDeployment,
19
+ listDeployments,
20
+ enableRoutingForProfile,
21
+ disableRoutingForProfile,
22
+ rollbackDeployment,
23
+ isRoutingEnabledForProfile,
24
+ getActiveCheckpointForProfile,
25
+ getDeploymentLineage,
26
+ getFullDeploymentRegistry,
27
+ getDeploymentRegistryStats,
28
+ assertSupportedProfile,
29
+ SUPPORTED_PROFILES,
30
+ type WorkerProfile,
31
+ } from '../../src/core/model-deployment-registry.js';
32
+ import {
33
+ registerTrainingRun,
34
+ startTrainingRun,
35
+ completeTrainingRun,
36
+ registerCheckpoint,
37
+ attachEvalSummary,
38
+ markCheckpointDeployable,
39
+ } from '../../src/core/model-training-registry.js';
40
+ import {
41
+ advancePromotion,
42
+ DEFAULT_BASELINE_METRICS,
43
+ } from '../../src/core/promotion-gate.js';
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Test Fixtures
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function makeTmpDir(): string {
50
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-deployment-registry-test-'));
51
+ }
52
+
53
+ function rmdir(dir: string): void {
54
+ try {
55
+ if (fs.existsSync(dir)) {
56
+ fs.rmSync(dir, { recursive: true, force: true });
57
+ }
58
+ } catch {
59
+ // Ignore
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Create a fully deployable reader-family checkpoint.
65
+ * Family keyword "reader" → satisfies local-reader profile constraint.
66
+ */
67
+ function setupDeployableReaderCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
68
+ const run = registerTrainingRun(tmpDir, {
69
+ targetModelFamily: 'claude-reader-latest',
70
+ datasetFingerprint: 'sha256-reader-001',
71
+ exportId: 'export-reader',
72
+ sampleCount: 30,
73
+ configFingerprint: 'cfg-v1',
74
+ });
75
+ const ck = registerCheckpoint(tmpDir, {
76
+ trainRunId: run.trainRunId,
77
+ targetModelFamily: 'claude-reader-latest',
78
+ artifactPath: '/checkpoints/reader-ck-001.safetensors',
79
+ });
80
+ attachEvalSummary(tmpDir, ck.checkpointId, {
81
+ evalId: 'eval-reader-001',
82
+ checkpointId: ck.checkpointId,
83
+ targetModelFamily: 'claude-reader-latest',
84
+ benchmarkId: 'bench-reader-001',
85
+ mode: 'reduced_prompt',
86
+ baselineScore: 0.5,
87
+ candidateScore: 0.65,
88
+ delta: 0.15,
89
+ verdict: 'pass',
90
+ });
91
+ startTrainingRun(tmpDir, run.trainRunId);
92
+ completeTrainingRun(tmpDir, run.trainRunId);
93
+ markCheckpointDeployable(tmpDir, ck.checkpointId, true);
94
+ // Advance through promotion gate (Phase 7: only local-reader is allowed)
95
+ advancePromotion(tmpDir, {
96
+ checkpointId: ck.checkpointId,
97
+ targetProfile: 'local-reader',
98
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
99
+ orchestratorReviewPassed: true,
100
+ reviewNote: 'Test approval',
101
+ });
102
+ return { runId: run.trainRunId, checkpointId: ck.checkpointId };
103
+ }
104
+
105
+ /**
106
+ * Create a fully deployable editor-family checkpoint.
107
+ * Family keyword "editor" → satisfies local-editor profile constraint.
108
+ */
109
+ function setupDeployableEditorCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
110
+ const run = registerTrainingRun(tmpDir, {
111
+ targetModelFamily: 'gpt-editor-v4',
112
+ datasetFingerprint: 'sha256-editor-001',
113
+ exportId: 'export-editor',
114
+ sampleCount: 30,
115
+ configFingerprint: 'cfg-v1',
116
+ });
117
+ const ck = registerCheckpoint(tmpDir, {
118
+ trainRunId: run.trainRunId,
119
+ targetModelFamily: 'gpt-editor-v4',
120
+ artifactPath: '/checkpoints/editor-ck-001.safetensors',
121
+ });
122
+ attachEvalSummary(tmpDir, ck.checkpointId, {
123
+ evalId: 'eval-editor-001',
124
+ checkpointId: ck.checkpointId,
125
+ targetModelFamily: 'gpt-editor-v4',
126
+ benchmarkId: 'bench-editor-001',
127
+ mode: 'reduced_prompt',
128
+ baselineScore: 0.5,
129
+ candidateScore: 0.7,
130
+ delta: 0.2,
131
+ verdict: 'pass',
132
+ });
133
+ startTrainingRun(tmpDir, run.trainRunId);
134
+ completeTrainingRun(tmpDir, run.trainRunId);
135
+ markCheckpointDeployable(tmpDir, ck.checkpointId, true);
136
+ // Advance through promotion gate
137
+ advancePromotion(tmpDir, {
138
+ checkpointId: ck.checkpointId,
139
+ targetProfile: 'local-editor',
140
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
141
+ orchestratorReviewPassed: true,
142
+ reviewNote: 'Test approval',
143
+ });
144
+ return { runId: run.trainRunId, checkpointId: ck.checkpointId };
145
+ }
146
+
147
+ /**
148
+ * Create a non-deployable checkpoint (no eval attached).
149
+ */
150
+ function setupNonDeployableCheckpoint(tmpDir: string): { runId: string; checkpointId: string } {
151
+ const run = registerTrainingRun(tmpDir, {
152
+ targetModelFamily: 'claude-reader-latest',
153
+ datasetFingerprint: 'sha256-abc',
154
+ exportId: 'export-abc',
155
+ sampleCount: 10,
156
+ configFingerprint: 'cfg-v1',
157
+ });
158
+ const ck = registerCheckpoint(tmpDir, {
159
+ trainRunId: run.trainRunId,
160
+ targetModelFamily: 'claude-reader-latest',
161
+ artifactPath: '/checkpoints/nondeployable.safetensors',
162
+ });
163
+ return { runId: run.trainRunId, checkpointId: ck.checkpointId };
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Tests: assertSupportedProfile
168
+ // ---------------------------------------------------------------------------
169
+
170
+ describe('ModelDeploymentRegistry assertSupportedProfile', () => {
171
+ it('accepts local-reader', () => {
172
+ expect(() => assertSupportedProfile('local-reader')).not.toThrow();
173
+ });
174
+
175
+ it('accepts local-editor', () => {
176
+ expect(() => assertSupportedProfile('local-editor')).not.toThrow();
177
+ });
178
+
179
+ it('rejects unknown profile', () => {
180
+ expect(() => assertSupportedProfile('local-architect')).toThrow('Unsupported worker profile');
181
+ });
182
+
183
+ it('rejects arbitrary strings', () => {
184
+ expect(() => assertSupportedProfile('gpt-5')).toThrow('Unsupported worker profile');
185
+ });
186
+ });
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Tests: bindCheckpointToWorkerProfile
190
+ // ---------------------------------------------------------------------------
191
+
192
+ describe('ModelDeploymentRegistry bindCheckpointToWorkerProfile', () => {
193
+ let tmpDir: string;
194
+
195
+ beforeEach(() => {
196
+ tmpDir = makeTmpDir();
197
+ });
198
+
199
+ afterEach(() => {
200
+ rmdir(tmpDir);
201
+ });
202
+
203
+ it('binds a deployable reader checkpoint to local-reader', () => {
204
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
205
+
206
+ const deployment = bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
207
+
208
+ expect(deployment.workerProfile).toBe('local-reader');
209
+ expect(deployment.activeCheckpointId).toBe(checkpointId);
210
+ expect(deployment.previousCheckpointId).toBeNull();
211
+ expect(deployment.routingEnabled).toBe(false);
212
+ expect(deployment.targetModelFamily).toBe('claude-reader-latest');
213
+ expect(deployment.deploymentId).toBeDefined();
214
+ expect(deployment.deployedAt).toBeDefined();
215
+ expect(deployment.updatedAt).toBeDefined();
216
+ });
217
+
218
+ it('binds a deployable editor checkpoint to local-editor', () => {
219
+ const { checkpointId } = setupDeployableEditorCheckpoint(tmpDir);
220
+
221
+ const deployment = bindCheckpointToWorkerProfile(tmpDir, 'local-editor', checkpointId);
222
+
223
+ expect(deployment.workerProfile).toBe('local-editor');
224
+ expect(deployment.activeCheckpointId).toBe(checkpointId);
225
+ expect(deployment.routingEnabled).toBe(false);
226
+ expect(deployment.targetModelFamily).toBe('gpt-editor-v4');
227
+ });
228
+
229
+ it('persists the deployment to disk', () => {
230
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
231
+
232
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
233
+
234
+ const retrieved = getDeployment(tmpDir, 'local-reader');
235
+ expect(retrieved).not.toBeNull();
236
+ expect(retrieved!.activeCheckpointId).toBe(checkpointId);
237
+ });
238
+
239
+ it('rejects binding to a non-deployable checkpoint', () => {
240
+ const { checkpointId } = setupNonDeployableCheckpoint(tmpDir);
241
+
242
+ expect(() =>
243
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId)
244
+ ).toThrow('not deployable');
245
+ });
246
+
247
+ it('rejects binding to a nonexistent checkpoint', () => {
248
+ expect(() =>
249
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', 'nonexistent-ck-id')
250
+ ).toThrow('not found');
251
+ });
252
+
253
+ it('rejects local-reader binding to an editor-family checkpoint', () => {
254
+ const { checkpointId } = setupDeployableEditorCheckpoint(tmpDir); // gpt-editor-v4
255
+
256
+ expect(() =>
257
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId)
258
+ ).toThrow('Family constraint violated');
259
+ });
260
+
261
+ it('rejects local-editor binding to a reader-family checkpoint', () => {
262
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir); // claude-reader-latest
263
+
264
+ expect(() =>
265
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', checkpointId)
266
+ ).toThrow('Family constraint violated');
267
+ });
268
+
269
+ it('rejects unsupported profile', () => {
270
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
271
+
272
+ expect(() =>
273
+ // @ts-expect-error — intentionally passing invalid profile
274
+ bindCheckpointToWorkerProfile(tmpDir, 'local-architect', checkpointId)
275
+ ).toThrow('Unsupported worker profile');
276
+ });
277
+
278
+ it('updating binding to a new checkpoint preserves previousCheckpointId', () => {
279
+ const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
280
+
281
+ // Set up a second reader checkpoint
282
+ const run2 = registerTrainingRun(tmpDir, {
283
+ targetModelFamily: 'claude-reader-latest',
284
+ datasetFingerprint: 'sha256-reader-002',
285
+ exportId: 'export-reader-2',
286
+ sampleCount: 30,
287
+ configFingerprint: 'cfg-v1',
288
+ });
289
+ const ck2 = registerCheckpoint(tmpDir, {
290
+ trainRunId: run2.trainRunId,
291
+ targetModelFamily: 'claude-reader-latest',
292
+ artifactPath: '/checkpoints/reader-ck-002.safetensors',
293
+ });
294
+ attachEvalSummary(tmpDir, ck2.checkpointId, {
295
+ evalId: 'eval-reader-002',
296
+ checkpointId: ck2.checkpointId,
297
+ targetModelFamily: 'claude-reader-latest',
298
+ benchmarkId: 'bench-reader-002',
299
+ mode: 'reduced_prompt',
300
+ baselineScore: 0.5,
301
+ candidateScore: 0.7,
302
+ delta: 0.2,
303
+ verdict: 'pass',
304
+ });
305
+ startTrainingRun(tmpDir, run2.trainRunId);
306
+ completeTrainingRun(tmpDir, run2.trainRunId);
307
+ markCheckpointDeployable(tmpDir, ck2.checkpointId, true);
308
+ advancePromotion(tmpDir, {
309
+ checkpointId: ck2.checkpointId,
310
+ targetProfile: 'local-reader',
311
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
312
+ orchestratorReviewPassed: true,
313
+ reviewNote: 'Test approval',
314
+ });
315
+
316
+ // First bind
317
+ const d1 = bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck1);
318
+ expect(d1.activeCheckpointId).toBe(ck1);
319
+ expect(d1.previousCheckpointId).toBeNull();
320
+
321
+ // Update bind to new checkpoint
322
+ const d2 = bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck2.checkpointId);
323
+ expect(d2.activeCheckpointId).toBe(ck2.checkpointId);
324
+ expect(d2.previousCheckpointId).toBe(ck1);
325
+ expect(d2.routingEnabled).toBe(false); // Reset to false on re-bind
326
+ expect(d2.deploymentId).toBe(d1.deploymentId); // Same deployment record
327
+ });
328
+
329
+ it('accepts a note on bind', () => {
330
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
331
+
332
+ const deployment = bindCheckpointToWorkerProfile(
333
+ tmpDir,
334
+ 'local-reader',
335
+ checkpointId,
336
+ 'Initial production deployment'
337
+ );
338
+
339
+ expect(deployment.note).toBe('Initial production deployment');
340
+ });
341
+ });
342
+
343
+ // ---------------------------------------------------------------------------
344
+ // Tests: getDeployment / listDeployments
345
+ // ---------------------------------------------------------------------------
346
+
347
+ describe('ModelDeploymentRegistry getDeployment / listDeployments', () => {
348
+ let tmpDir: string;
349
+
350
+ beforeEach(() => {
351
+ tmpDir = makeTmpDir();
352
+ });
353
+
354
+ afterEach(() => {
355
+ rmdir(tmpDir);
356
+ });
357
+
358
+ it('returns null for a profile with no deployment', () => {
359
+ const deployment = getDeployment(tmpDir, 'local-reader');
360
+ expect(deployment).toBeNull();
361
+ });
362
+
363
+ it('getDeployment returns the bound deployment', () => {
364
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
365
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
366
+
367
+ const deployment = getDeployment(tmpDir, 'local-reader');
368
+ expect(deployment).not.toBeNull();
369
+ expect(deployment!.workerProfile).toBe('local-reader');
370
+ });
371
+
372
+ it('listDeployments returns all deployments sorted by updatedAt desc', () => {
373
+ const { checkpointId: ckReader } = setupDeployableReaderCheckpoint(tmpDir);
374
+ const { checkpointId: ckEditor } = setupDeployableEditorCheckpoint(tmpDir);
375
+
376
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ckReader);
377
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', ckEditor);
378
+
379
+ const deployments = listDeployments(tmpDir);
380
+ expect(deployments).toHaveLength(2);
381
+ // Most recently updated is last in the list (sorted asc by updatedAt in memory)
382
+ expect(deployments[0].workerProfile).toBe('local-editor'); // bound second
383
+ });
384
+
385
+ it('listDeployments filters by workerProfile', () => {
386
+ const { checkpointId: ckReader } = setupDeployableReaderCheckpoint(tmpDir);
387
+ const { checkpointId: ckEditor } = setupDeployableEditorCheckpoint(tmpDir);
388
+
389
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ckReader);
390
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', ckEditor);
391
+
392
+ const readerDeployments = listDeployments(tmpDir, { workerProfile: 'local-reader' });
393
+ expect(readerDeployments).toHaveLength(1);
394
+ expect(readerDeployments[0].workerProfile).toBe('local-reader');
395
+ });
396
+
397
+ it('listDeployments filters by routingEnabled', () => {
398
+ const { checkpointId: ckReader } = setupDeployableReaderCheckpoint(tmpDir);
399
+ const { checkpointId: ckEditor } = setupDeployableEditorCheckpoint(tmpDir);
400
+
401
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ckReader);
402
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', ckEditor);
403
+
404
+ enableRoutingForProfile(tmpDir, 'local-editor');
405
+
406
+ const enabled = listDeployments(tmpDir, { routingEnabled: true });
407
+ const disabled = listDeployments(tmpDir, { routingEnabled: false });
408
+
409
+ expect(enabled).toHaveLength(1);
410
+ expect(enabled[0].workerProfile).toBe('local-editor');
411
+ expect(disabled).toHaveLength(1);
412
+ expect(disabled[0].workerProfile).toBe('local-reader');
413
+ });
414
+ });
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // Tests: enableRoutingForProfile / disableRoutingForProfile
418
+ // ---------------------------------------------------------------------------
419
+
420
+ describe('ModelDeploymentRegistry routing enable/disable', () => {
421
+ let tmpDir: string;
422
+
423
+ beforeEach(() => {
424
+ tmpDir = makeTmpDir();
425
+ });
426
+
427
+ afterEach(() => {
428
+ rmdir(tmpDir);
429
+ });
430
+
431
+ it('routingEnabled starts false after bind', () => {
432
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
433
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
434
+
435
+ const deployment = getDeployment(tmpDir, 'local-reader');
436
+ expect(deployment!.routingEnabled).toBe(false);
437
+ });
438
+
439
+ it('enableRoutingForProfile sets routingEnabled to true', () => {
440
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
441
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
442
+
443
+ const deployment = enableRoutingForProfile(tmpDir, 'local-reader');
444
+
445
+ expect(deployment.routingEnabled).toBe(true);
446
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(true);
447
+ });
448
+
449
+ it('disableRoutingForProfile sets routingEnabled to false', () => {
450
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
451
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
452
+ enableRoutingForProfile(tmpDir, 'local-reader');
453
+
454
+ const deployment = disableRoutingForProfile(tmpDir, 'local-reader');
455
+
456
+ expect(deployment.routingEnabled).toBe(false);
457
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(false);
458
+ });
459
+
460
+ it('enableRoutingForProfile fails if no deployment exists', () => {
461
+ expect(() => enableRoutingForProfile(tmpDir, 'local-reader')).toThrow(
462
+ 'no deployment found'
463
+ );
464
+ });
465
+
466
+ it('enableRoutingForProfile fails if active checkpoint was rolled back to null', () => {
467
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
468
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
469
+ // Manually set activeCheckpointId to null via direct registry manipulation
470
+ // to simulate a profile bound to a checkpoint that was subsequently revoked
471
+ // (an out-of-scope Phase 5 operation would normally do this).
472
+ // This tests the guard in enableRoutingForProfile that checks activeCheckpointId.
473
+ const registryPath = path.join(tmpDir, '.state', 'nocturnal', 'deployment-registry.json');
474
+ const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
475
+ registry.deployments[0].activeCheckpointId = null;
476
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));
477
+ expect(() => enableRoutingForProfile(tmpDir, 'local-reader')).toThrow(
478
+ 'no active checkpoint'
479
+ );
480
+ });
481
+
482
+ it('isRoutingEnabledForProfile returns false for unknown profile', () => {
483
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(false);
484
+ });
485
+
486
+ it('isRoutingEnabledForProfile returns false when active checkpoint is revoked', () => {
487
+ // Set up a bound and enabled deployment
488
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
489
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
490
+ enableRoutingForProfile(tmpDir, 'local-reader');
491
+
492
+ // Confirm routing is enabled
493
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(true);
494
+
495
+ // Revoke the checkpoint (simulates re-evaluation failure or deprecation)
496
+ markCheckpointDeployable(tmpDir, checkpointId, false);
497
+
498
+ // GOVERNANCE: isRoutingEnabledForProfile must return false after revocation
499
+ // even though the routingEnabled toggle is still true in the deployment registry
500
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(false);
501
+ });
502
+
503
+ it('getActiveCheckpointForProfile returns checkpoint ID when bound', () => {
504
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
505
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
506
+
507
+ expect(getActiveCheckpointForProfile(tmpDir, 'local-reader')).toBe(checkpointId);
508
+ });
509
+
510
+ it('getActiveCheckpointForProfile returns null when no deployment', () => {
511
+ expect(getActiveCheckpointForProfile(tmpDir, 'local-reader')).toBeNull();
512
+ });
513
+
514
+ it('enable/disable does not change activeCheckpointId', () => {
515
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
516
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
517
+ enableRoutingForProfile(tmpDir, 'local-reader');
518
+ disableRoutingForProfile(tmpDir, 'local-reader');
519
+
520
+ expect(getActiveCheckpointForProfile(tmpDir, 'local-reader')).toBe(checkpointId);
521
+ });
522
+ });
523
+
524
+ // ---------------------------------------------------------------------------
525
+ // Tests: rollbackDeployment
526
+ // ---------------------------------------------------------------------------
527
+
528
+ describe('ModelDeploymentRegistry rollbackDeployment', () => {
529
+ let tmpDir: string;
530
+
531
+ beforeEach(() => {
532
+ tmpDir = makeTmpDir();
533
+ });
534
+
535
+ afterEach(() => {
536
+ rmdir(tmpDir);
537
+ });
538
+
539
+ it('rollback fails when there is no previous checkpoint', () => {
540
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
541
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
542
+
543
+ expect(() => rollbackDeployment(tmpDir, 'local-reader')).toThrow(
544
+ 'no previous checkpoint available'
545
+ );
546
+ });
547
+
548
+ it('rollback fails when no deployment exists', () => {
549
+ expect(() => rollbackDeployment(tmpDir, 'local-reader')).toThrow(
550
+ 'no deployment found'
551
+ );
552
+ });
553
+
554
+ it('rollback to previous checkpoint succeeds and disables routing', () => {
555
+ // Set up ck1
556
+ const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
557
+
558
+ // Set up ck2
559
+ const run2 = registerTrainingRun(tmpDir, {
560
+ targetModelFamily: 'claude-reader-latest',
561
+ datasetFingerprint: 'sha256-reader-002',
562
+ exportId: 'export-reader-2',
563
+ sampleCount: 30,
564
+ configFingerprint: 'cfg-v1',
565
+ });
566
+ const ck2 = registerCheckpoint(tmpDir, {
567
+ trainRunId: run2.trainRunId,
568
+ targetModelFamily: 'claude-reader-latest',
569
+ artifactPath: '/checkpoints/reader-ck-002.safetensors',
570
+ });
571
+ attachEvalSummary(tmpDir, ck2.checkpointId, {
572
+ evalId: 'eval-reader-002',
573
+ checkpointId: ck2.checkpointId,
574
+ targetModelFamily: 'claude-reader-latest',
575
+ benchmarkId: 'bench-reader-002',
576
+ mode: 'reduced_prompt',
577
+ baselineScore: 0.5,
578
+ candidateScore: 0.7,
579
+ delta: 0.2,
580
+ verdict: 'pass',
581
+ });
582
+ startTrainingRun(tmpDir, run2.trainRunId);
583
+ completeTrainingRun(tmpDir, run2.trainRunId);
584
+ markCheckpointDeployable(tmpDir, ck2.checkpointId, true);
585
+ advancePromotion(tmpDir, {
586
+ checkpointId: ck2.checkpointId,
587
+ targetProfile: 'local-reader',
588
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
589
+ orchestratorReviewPassed: true,
590
+ reviewNote: 'Test approval',
591
+ });
592
+
593
+ // Bind ck1, then update to ck2
594
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck1);
595
+ enableRoutingForProfile(tmpDir, 'local-reader');
596
+ // Note: re-binding resets routingEnabled to false — must re-enable
597
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck2.checkpointId, 'promoted ck2');
598
+ enableRoutingForProfile(tmpDir, 'local-reader');
599
+
600
+ // Verify ck2 is active and routing is enabled
601
+ expect(getActiveCheckpointForProfile(tmpDir, 'local-reader')).toBe(ck2.checkpointId);
602
+ expect(isRoutingEnabledForProfile(tmpDir, 'local-reader')).toBe(true);
603
+
604
+ // Roll back
605
+ const rolledBack = rollbackDeployment(tmpDir, 'local-reader', 'rolled back to ck1');
606
+
607
+ expect(rolledBack.activeCheckpointId).toBe(ck1);
608
+ expect(rolledBack.previousCheckpointId).toBe(ck2.checkpointId); // ck2 becomes previous
609
+ expect(rolledBack.routingEnabled).toBe(false); // Always reset
610
+ expect(rolledBack.note).toBe('rolled back to ck1');
611
+ });
612
+
613
+ it('rollback fails when previous checkpoint no longer exists', () => {
614
+ const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
615
+
616
+ const run2 = registerTrainingRun(tmpDir, {
617
+ targetModelFamily: 'claude-reader-latest',
618
+ datasetFingerprint: 'sha256-reader-002',
619
+ exportId: 'export-reader-2',
620
+ sampleCount: 30,
621
+ configFingerprint: 'cfg-v1',
622
+ });
623
+ const ck2 = registerCheckpoint(tmpDir, {
624
+ trainRunId: run2.trainRunId,
625
+ targetModelFamily: 'claude-reader-latest',
626
+ artifactPath: '/checkpoints/reader-ck-002.safetensors',
627
+ });
628
+ attachEvalSummary(tmpDir, ck2.checkpointId, {
629
+ evalId: 'eval-reader-002',
630
+ checkpointId: ck2.checkpointId,
631
+ targetModelFamily: 'claude-reader-latest',
632
+ benchmarkId: 'bench-reader-002',
633
+ mode: 'reduced_prompt',
634
+ baselineScore: 0.5,
635
+ candidateScore: 0.7,
636
+ delta: 0.2,
637
+ verdict: 'pass',
638
+ });
639
+ startTrainingRun(tmpDir, run2.trainRunId);
640
+ completeTrainingRun(tmpDir, run2.trainRunId);
641
+ markCheckpointDeployable(tmpDir, ck2.checkpointId, true);
642
+ advancePromotion(tmpDir, {
643
+ checkpointId: ck2.checkpointId,
644
+ targetProfile: 'local-reader',
645
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
646
+ orchestratorReviewPassed: true,
647
+ reviewNote: 'Test approval',
648
+ });
649
+
650
+ // Bind ck1, then ck2 on top — previousCheckpointId = ck1
651
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck1);
652
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck2.checkpointId);
653
+
654
+ // Simulate ck1 being deleted from the training registry (outside Phase 5 scope)
655
+ // by directly editing the training-registry.json
656
+ const trainingRegistryPath = path.join(tmpDir, '.state', 'nocturnal', 'training-registry.json');
657
+ const trainingRegistry = JSON.parse(fs.readFileSync(trainingRegistryPath, 'utf-8'));
658
+ trainingRegistry.checkpoints = trainingRegistry.checkpoints.filter(
659
+ (ck: { checkpointId: string }) => ck.checkpointId !== ck1
660
+ );
661
+ fs.writeFileSync(trainingRegistryPath, JSON.stringify(trainingRegistry, null, 2));
662
+
663
+ // Now rollbackDeployment must fail because ck1 no longer exists
664
+ expect(() => rollbackDeployment(tmpDir, 'local-reader')).toThrow(
665
+ `no longer exists in the training registry`
666
+ );
667
+ });
668
+
669
+ it('can roll back twice (ck1 → ck2 → ck1)', () => {
670
+ const { checkpointId: ck1 } = setupDeployableReaderCheckpoint(tmpDir);
671
+
672
+ const run2 = registerTrainingRun(tmpDir, {
673
+ targetModelFamily: 'claude-reader-latest',
674
+ datasetFingerprint: 'sha256-reader-002',
675
+ exportId: 'export-reader-2',
676
+ sampleCount: 30,
677
+ configFingerprint: 'cfg-v1',
678
+ });
679
+ const ck2 = registerCheckpoint(tmpDir, {
680
+ trainRunId: run2.trainRunId,
681
+ targetModelFamily: 'claude-reader-latest',
682
+ artifactPath: '/checkpoints/reader-ck-002.safetensors',
683
+ });
684
+ attachEvalSummary(tmpDir, ck2.checkpointId, {
685
+ evalId: 'eval-reader-002',
686
+ checkpointId: ck2.checkpointId,
687
+ targetModelFamily: 'claude-reader-latest',
688
+ benchmarkId: 'bench-reader-002',
689
+ mode: 'reduced_prompt',
690
+ baselineScore: 0.5,
691
+ candidateScore: 0.7,
692
+ delta: 0.2,
693
+ verdict: 'pass',
694
+ });
695
+ startTrainingRun(tmpDir, run2.trainRunId);
696
+ completeTrainingRun(tmpDir, run2.trainRunId);
697
+ markCheckpointDeployable(tmpDir, ck2.checkpointId, true);
698
+ advancePromotion(tmpDir, {
699
+ checkpointId: ck2.checkpointId,
700
+ targetProfile: 'local-reader',
701
+ baselineMetrics: DEFAULT_BASELINE_METRICS,
702
+ orchestratorReviewPassed: true,
703
+ reviewNote: 'Test approval',
704
+ });
705
+
706
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck1);
707
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ck2.checkpointId); // ck1 is now previous
708
+ const r1 = rollbackDeployment(tmpDir, 'local-reader'); // back to ck1, ck2 is now previous
709
+ expect(r1.activeCheckpointId).toBe(ck1);
710
+
711
+ // Enable routing, then roll back again to ck2
712
+ enableRoutingForProfile(tmpDir, 'local-reader');
713
+ const r2 = rollbackDeployment(tmpDir, 'local-reader'); // back to ck2
714
+ expect(r2.activeCheckpointId).toBe(ck2.checkpointId);
715
+ expect(r2.previousCheckpointId).toBe(ck1); // ck1 is still tracked
716
+ expect(r2.routingEnabled).toBe(false);
717
+ });
718
+ });
719
+
720
+ // ---------------------------------------------------------------------------
721
+ // Tests: Lineage Queries
722
+ // ---------------------------------------------------------------------------
723
+
724
+ describe('ModelDeploymentRegistry lineage queries', () => {
725
+ let tmpDir: string;
726
+
727
+ beforeEach(() => {
728
+ tmpDir = makeTmpDir();
729
+ });
730
+
731
+ afterEach(() => {
732
+ rmdir(tmpDir);
733
+ });
734
+
735
+ it('getDeploymentLineage returns deployment and active checkpoint', () => {
736
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
737
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId, 'reader v1');
738
+
739
+ const lineage = getDeploymentLineage(tmpDir, 'local-reader');
740
+
741
+ expect(lineage).not.toBeNull();
742
+ expect(lineage!.deployment.workerProfile).toBe('local-reader');
743
+ expect(lineage!.deployment.note).toBe('reader v1');
744
+ expect(lineage!.activeCheckpoint).not.toBeNull();
745
+ expect(lineage!.activeCheckpoint!.checkpointId).toBe(checkpointId);
746
+ });
747
+
748
+ it('getDeploymentLineage returns null for unknown profile', () => {
749
+ const lineage = getDeploymentLineage(tmpDir, 'local-reader');
750
+ expect(lineage).toBeNull();
751
+ });
752
+
753
+ it('rollback exhausts history when only one checkpoint exists (no previous to roll back to)', () => {
754
+ // Bind a single checkpoint — there is no previous to roll back to
755
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
756
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
757
+
758
+ // Attempting to roll back should fail because previousCheckpointId is null
759
+ expect(() => rollbackDeployment(tmpDir, 'local-reader')).toThrow(
760
+ 'no previous checkpoint available'
761
+ );
762
+
763
+ // The active checkpoint is still set (rollback didn't happen)
764
+ expect(getActiveCheckpointForProfile(tmpDir, 'local-reader')).toBe(checkpointId);
765
+ });
766
+ });
767
+
768
+ // ---------------------------------------------------------------------------
769
+ // Tests: Stats
770
+ // ---------------------------------------------------------------------------
771
+
772
+ describe('ModelDeploymentRegistry stats', () => {
773
+ let tmpDir: string;
774
+
775
+ beforeEach(() => {
776
+ tmpDir = makeTmpDir();
777
+ });
778
+
779
+ afterEach(() => {
780
+ rmdir(tmpDir);
781
+ });
782
+
783
+ it('returns zeros for empty registry', () => {
784
+ const stats = getDeploymentRegistryStats(tmpDir);
785
+ expect(stats.totalDeployments).toBe(0);
786
+ expect(stats.activeDeployments).toBe(0);
787
+ expect(stats.profilesWithBindings).toBe(0);
788
+ expect(stats.profilesWithRoutingEnabled).toBe(0);
789
+ });
790
+
791
+ it('counts profiles with bindings and routing enabled', () => {
792
+ const { checkpointId: ckReader } = setupDeployableReaderCheckpoint(tmpDir);
793
+ const { checkpointId: ckEditor } = setupDeployableEditorCheckpoint(tmpDir);
794
+
795
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ckReader);
796
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', ckEditor);
797
+ enableRoutingForProfile(tmpDir, 'local-editor');
798
+
799
+ const stats = getDeploymentRegistryStats(tmpDir);
800
+ expect(stats.totalDeployments).toBe(2);
801
+ expect(stats.profilesWithBindings).toBe(2);
802
+ expect(stats.activeDeployments).toBe(1);
803
+ expect(stats.profilesWithRoutingEnabled).toBe(1);
804
+ });
805
+ });
806
+
807
+ // ---------------------------------------------------------------------------
808
+ // Tests: Registry Persistence
809
+ // ---------------------------------------------------------------------------
810
+
811
+ describe('ModelDeploymentRegistry persistence', () => {
812
+ let tmpDir: string;
813
+
814
+ beforeEach(() => {
815
+ tmpDir = makeTmpDir();
816
+ });
817
+
818
+ afterEach(() => {
819
+ rmdir(tmpDir);
820
+ });
821
+
822
+ it('getFullDeploymentRegistry returns all deployment records', () => {
823
+ const { checkpointId: ckReader } = setupDeployableReaderCheckpoint(tmpDir);
824
+ const { checkpointId: ckEditor } = setupDeployableEditorCheckpoint(tmpDir);
825
+
826
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', ckReader);
827
+ bindCheckpointToWorkerProfile(tmpDir, 'local-editor', ckEditor);
828
+
829
+ const registry = getFullDeploymentRegistry(tmpDir);
830
+ expect(registry.deployments).toHaveLength(2);
831
+ });
832
+
833
+ it('registry persists to disk as JSON', () => {
834
+ const { checkpointId } = setupDeployableReaderCheckpoint(tmpDir);
835
+ bindCheckpointToWorkerProfile(tmpDir, 'local-reader', checkpointId);
836
+
837
+ const registryPath = path.join(tmpDir, '.state', 'nocturnal', 'deployment-registry.json');
838
+ expect(fs.existsSync(registryPath)).toBe(true);
839
+
840
+ const raw = fs.readFileSync(registryPath, 'utf-8');
841
+ const parsed = JSON.parse(raw);
842
+ expect(parsed.deployments).toHaveLength(1);
843
+ expect(parsed.deployments[0].workerProfile).toBe('local-reader');
844
+ });
845
+ });