principles-disciple 1.8.1 → 1.8.3

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 (508) 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/en/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  138. package/templates/langs/en/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  139. package/templates/langs/en/skills/ai-sprint-orchestration/SKILL.md +67 -0
  140. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  141. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  142. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  143. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  144. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  145. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  146. package/templates/langs/en/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  147. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  148. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  149. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  150. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  151. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  152. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  153. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  154. package/templates/langs/zh/skills/ai-sprint-orchestration/EXAMPLES.md +63 -0
  155. package/templates/langs/zh/skills/ai-sprint-orchestration/REFERENCE.md +136 -0
  156. package/templates/langs/zh/skills/ai-sprint-orchestration/SKILL.md +67 -0
  157. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +214 -0
  158. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +107 -0
  159. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +107 -0
  160. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +105 -0
  161. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +108 -0
  162. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1-acceptance-checklist.md +58 -0
  163. package/templates/langs/zh/skills/ai-sprint-orchestration/references/workflow-v1.4-work-unit-handoff.md +190 -0
  164. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -0
  165. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/archive.mjs +310 -0
  166. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/contract-enforcement.mjs +683 -0
  167. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/decision.mjs +604 -0
  168. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/state-store.mjs +32 -0
  169. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +707 -0
  170. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +3419 -0
  171. package/templates/langs/zh/skills/ai-sprint-orchestration/test/archive.test.mjs +230 -0
  172. package/templates/langs/zh/skills/ai-sprint-orchestration/test/contract-enforcement.test.mjs +672 -0
  173. package/templates/langs/zh/skills/ai-sprint-orchestration/test/decision.test.mjs +1321 -0
  174. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +1419 -0
  175. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  176. package/templates/pain_settings.json +2 -1
  177. package/tests/README.md +120 -0
  178. package/tests/build-artifacts.test.ts +111 -0
  179. package/tests/commands/evolution-status.test.ts +222 -0
  180. package/tests/commands/evolver.test.ts +22 -0
  181. package/tests/commands/export.test.ts +78 -0
  182. package/tests/commands/nocturnal-review.test.ts +448 -0
  183. package/tests/commands/nocturnal-train.test.ts +97 -0
  184. package/tests/commands/pain.test.ts +108 -0
  185. package/tests/commands/samples.test.ts +65 -0
  186. package/tests/commands/strategy.test.ts +34 -0
  187. package/tests/commands/thinking-os.test.ts +88 -0
  188. package/tests/core/adaptive-thresholds.test.ts +261 -0
  189. package/tests/core/config-service.test.ts +89 -0
  190. package/tests/core/config.test.ts +90 -0
  191. package/tests/core/control-ui-db.test.ts +75 -0
  192. package/tests/core/core-template-guidance.test.ts +21 -0
  193. package/tests/core/detection-funnel.test.ts +63 -0
  194. package/tests/core/detection-service.test.ts +50 -0
  195. package/tests/core/dictionary-service.test.ts +116 -0
  196. package/tests/core/dictionary.test.ts +168 -0
  197. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  198. package/tests/core/event-log.test.ts +181 -0
  199. package/tests/core/evolution-e2e.test.ts +58 -0
  200. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  201. package/tests/core/evolution-engine.test.ts +562 -0
  202. package/tests/core/evolution-logger.test.ts +148 -0
  203. package/tests/core/evolution-migration.test.ts +50 -0
  204. package/tests/core/evolution-paths.test.ts +21 -0
  205. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  206. package/tests/core/evolution-reducer.test.ts +180 -0
  207. package/tests/core/evolution-types-loop.test.ts +48 -0
  208. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  209. package/tests/core/external-training-contract.test.ts +463 -0
  210. package/tests/core/focus-history.test.ts +682 -0
  211. package/tests/core/init-flatten.test.ts +69 -0
  212. package/tests/core/init-refactor.test.ts +87 -0
  213. package/tests/core/init-v1.3.test.ts +46 -0
  214. package/tests/core/init.test.ts +190 -0
  215. package/tests/core/local-worker-routing.test.ts +757 -0
  216. package/tests/core/migration.test.ts +84 -0
  217. package/tests/core/model-deployment-registry.test.ts +845 -0
  218. package/tests/core/model-training-registry.test.ts +889 -0
  219. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  220. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  221. package/tests/core/nocturnal-compliance.test.ts +646 -0
  222. package/tests/core/nocturnal-dataset.test.ts +892 -0
  223. package/tests/core/nocturnal-executability.test.ts +357 -0
  224. package/tests/core/nocturnal-export.test.ts +462 -0
  225. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  226. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  227. package/tests/core/nocturnal-trinity.test.ts +953 -0
  228. package/tests/core/pain.test.ts +33 -0
  229. package/tests/core/path-resolver.test.ts +57 -0
  230. package/tests/core/paths-refactor.test.ts +42 -0
  231. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  232. package/tests/core/principle-training-state.test.ts +712 -0
  233. package/tests/core/profile.test.ts +56 -0
  234. package/tests/core/promotion-gate.test.ts +556 -0
  235. package/tests/core/risk-calculator.test.ts +168 -0
  236. package/tests/core/session-tracker.test.ts +191 -0
  237. package/tests/core/training-program.test.ts +472 -0
  238. package/tests/core/trajectory.test.ts +265 -0
  239. package/tests/core/workspace-context-factory.test.ts +18 -0
  240. package/tests/core/workspace-context.test.ts +134 -0
  241. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  242. package/tests/fixtures/production-compatibility.test.ts +147 -0
  243. package/tests/fixtures/production-mock-generator.ts +282 -0
  244. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  245. package/tests/hooks/bash-risk.test.ts +81 -0
  246. package/tests/hooks/edit-verification.test.ts +678 -0
  247. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  248. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  249. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  250. package/tests/hooks/gate.test.ts +271 -0
  251. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  252. package/tests/hooks/gfi-gate.test.ts +669 -0
  253. package/tests/hooks/lifecycle.test.ts +248 -0
  254. package/tests/hooks/llm.test.ts +308 -0
  255. package/tests/hooks/message-sanitize.test.ts +36 -0
  256. package/tests/hooks/pain.test.ts +141 -0
  257. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  258. package/tests/hooks/prompt.test.ts +1411 -0
  259. package/tests/hooks/subagent.test.ts +467 -0
  260. package/tests/hooks/thinking-gate.test.ts +313 -0
  261. package/tests/http/principles-console-route.test.ts +140 -0
  262. package/tests/hygiene-tracker.test.ts +77 -0
  263. package/tests/index.integration.test.ts +179 -0
  264. package/tests/index.shadow-routing.integration.test.ts +140 -0
  265. package/tests/index.test.ts +9 -0
  266. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  267. package/tests/service/control-ui-query-service.test.ts +121 -0
  268. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  269. package/tests/service/evolution-worker.test.ts +585 -0
  270. package/tests/service/nocturnal-runtime.test.ts +470 -0
  271. package/tests/service/nocturnal-service.test.ts +577 -0
  272. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  273. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  274. package/tests/service/phase3-input-filter.test.ts +289 -0
  275. package/tests/service/runtime-summary-service.test.ts +919 -0
  276. package/tests/task-compliance.test.ts +166 -0
  277. package/tests/test-utils.ts +48 -0
  278. package/tests/tools/critique-prompt.test.ts +260 -0
  279. package/tests/tools/deep-reflect.test.ts +232 -0
  280. package/tests/tools/model-index.test.ts +246 -0
  281. package/tests/ui/app.test.tsx +114 -0
  282. package/tests/utils/file-lock.test.ts +407 -0
  283. package/tests/utils/hashing.test.ts +32 -0
  284. package/tests/utils/io.test.ts +39 -0
  285. package/tests/utils/nlp.test.ts +53 -0
  286. package/tests/utils/plugin-logger.test.ts +156 -0
  287. package/tsconfig.json +16 -0
  288. package/tsconfig.tsbuildinfo +1 -0
  289. package/ui/src/App.tsx +45 -0
  290. package/ui/src/api.ts +216 -0
  291. package/ui/src/charts.tsx +586 -0
  292. package/ui/src/components/ErrorState.tsx +6 -0
  293. package/ui/src/components/Loading.tsx +13 -0
  294. package/ui/src/components/ProtectedRoute.tsx +12 -0
  295. package/ui/src/components/Shell.tsx +91 -0
  296. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  297. package/ui/src/components/index.ts +5 -0
  298. package/ui/src/context/auth.tsx +80 -0
  299. package/ui/src/context/theme.tsx +66 -0
  300. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  301. package/ui/src/i18n/ui.ts +363 -0
  302. package/ui/src/main.tsx +16 -0
  303. package/ui/src/pages/EvolutionPage.tsx +352 -0
  304. package/ui/src/pages/FeedbackPage.tsx +140 -0
  305. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  306. package/ui/src/pages/LoginPage.tsx +88 -0
  307. package/ui/src/pages/OverviewPage.tsx +238 -0
  308. package/ui/src/pages/SamplesPage.tsx +174 -0
  309. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  310. package/ui/src/styles.css +1661 -0
  311. package/ui/src/types.ts +368 -0
  312. package/ui/src/utils/format.ts +15 -0
  313. package/vitest.config.ts +23 -0
  314. package/dist/commands/capabilities.d.ts +0 -3
  315. package/dist/commands/capabilities.js +0 -73
  316. package/dist/commands/context.d.ts +0 -5
  317. package/dist/commands/evolution-status.d.ts +0 -4
  318. package/dist/commands/evolution-status.js +0 -117
  319. package/dist/commands/evolver.d.ts +0 -9
  320. package/dist/commands/evolver.js +0 -26
  321. package/dist/commands/export.d.ts +0 -2
  322. package/dist/commands/export.js +0 -98
  323. package/dist/commands/focus.d.ts +0 -14
  324. package/dist/commands/focus.js +0 -457
  325. package/dist/commands/nocturnal-review.d.ts +0 -24
  326. package/dist/commands/nocturnal-review.js +0 -265
  327. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  328. package/dist/commands/nocturnal-rollout.js +0 -671
  329. package/dist/commands/nocturnal-train.d.ts +0 -25
  330. package/dist/commands/nocturnal-train.js +0 -919
  331. package/dist/commands/pain.d.ts +0 -5
  332. package/dist/commands/principle-rollback.d.ts +0 -4
  333. package/dist/commands/principle-rollback.js +0 -22
  334. package/dist/commands/rollback.d.ts +0 -19
  335. package/dist/commands/samples.d.ts +0 -2
  336. package/dist/commands/samples.js +0 -55
  337. package/dist/commands/strategy.d.ts +0 -3
  338. package/dist/commands/strategy.js +0 -29
  339. package/dist/commands/thinking-os.d.ts +0 -2
  340. package/dist/config/defaults/runtime.d.ts +0 -40
  341. package/dist/config/errors.d.ts +0 -84
  342. package/dist/config/errors.js +0 -94
  343. package/dist/config/index.js +0 -7
  344. package/dist/constants/diagnostician.d.ts +0 -12
  345. package/dist/constants/diagnostician.js +0 -56
  346. package/dist/constants/tools.d.ts +0 -17
  347. package/dist/constants/tools.js +0 -54
  348. package/dist/core/adaptive-thresholds.d.ts +0 -186
  349. package/dist/core/adaptive-thresholds.js +0 -300
  350. package/dist/core/config-service.d.ts +0 -15
  351. package/dist/core/config.d.ts +0 -129
  352. package/dist/core/control-ui-db.d.ts +0 -95
  353. package/dist/core/control-ui-db.js +0 -292
  354. package/dist/core/detection-funnel.d.ts +0 -33
  355. package/dist/core/detection-service.d.ts +0 -15
  356. package/dist/core/dictionary-service.d.ts +0 -15
  357. package/dist/core/dictionary.d.ts +0 -38
  358. package/dist/core/event-log.d.ts +0 -82
  359. package/dist/core/event-log.js +0 -463
  360. package/dist/core/evolution-engine.d.ts +0 -118
  361. package/dist/core/evolution-engine.js +0 -464
  362. package/dist/core/evolution-logger.d.ts +0 -137
  363. package/dist/core/evolution-logger.js +0 -256
  364. package/dist/core/evolution-migration.d.ts +0 -5
  365. package/dist/core/evolution-migration.js +0 -65
  366. package/dist/core/evolution-reducer.d.ts +0 -98
  367. package/dist/core/evolution-reducer.js +0 -465
  368. package/dist/core/evolution-types.d.ts +0 -287
  369. package/dist/core/evolution-types.js +0 -78
  370. package/dist/core/external-training-contract.d.ts +0 -276
  371. package/dist/core/external-training-contract.js +0 -269
  372. package/dist/core/focus-history.d.ts +0 -210
  373. package/dist/core/focus-history.js +0 -1185
  374. package/dist/core/hygiene/tracker.d.ts +0 -22
  375. package/dist/core/hygiene/tracker.js +0 -106
  376. package/dist/core/init.d.ts +0 -12
  377. package/dist/core/local-worker-routing.d.ts +0 -175
  378. package/dist/core/local-worker-routing.js +0 -525
  379. package/dist/core/migration.d.ts +0 -6
  380. package/dist/core/model-deployment-registry.d.ts +0 -218
  381. package/dist/core/model-deployment-registry.js +0 -503
  382. package/dist/core/model-training-registry.d.ts +0 -295
  383. package/dist/core/model-training-registry.js +0 -475
  384. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  385. package/dist/core/nocturnal-arbiter.js +0 -534
  386. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  387. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  388. package/dist/core/nocturnal-compliance.d.ts +0 -175
  389. package/dist/core/nocturnal-compliance.js +0 -824
  390. package/dist/core/nocturnal-dataset.d.ts +0 -224
  391. package/dist/core/nocturnal-dataset.js +0 -443
  392. package/dist/core/nocturnal-executability.d.ts +0 -85
  393. package/dist/core/nocturnal-executability.js +0 -331
  394. package/dist/core/nocturnal-export.d.ts +0 -124
  395. package/dist/core/nocturnal-export.js +0 -275
  396. package/dist/core/nocturnal-paths.d.ts +0 -124
  397. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  398. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  399. package/dist/core/nocturnal-trinity.d.ts +0 -311
  400. package/dist/core/nocturnal-trinity.js +0 -880
  401. package/dist/core/pain.d.ts +0 -4
  402. package/dist/core/pain.js +0 -70
  403. package/dist/core/path-resolver.d.ts +0 -46
  404. package/dist/core/paths.d.ts +0 -65
  405. package/dist/core/principle-training-state.d.ts +0 -121
  406. package/dist/core/principle-training-state.js +0 -321
  407. package/dist/core/profile.d.ts +0 -62
  408. package/dist/core/profile.js +0 -210
  409. package/dist/core/promotion-gate.d.ts +0 -238
  410. package/dist/core/promotion-gate.js +0 -529
  411. package/dist/core/risk-calculator.d.ts +0 -22
  412. package/dist/core/session-tracker.d.ts +0 -101
  413. package/dist/core/shadow-observation-registry.d.ts +0 -217
  414. package/dist/core/shadow-observation-registry.js +0 -308
  415. package/dist/core/system-logger.d.ts +0 -8
  416. package/dist/core/thinking-models.d.ts +0 -38
  417. package/dist/core/thinking-models.js +0 -170
  418. package/dist/core/training-program.d.ts +0 -233
  419. package/dist/core/training-program.js +0 -433
  420. package/dist/core/trajectory.d.ts +0 -411
  421. package/dist/core/trajectory.js +0 -1307
  422. package/dist/core/workspace-context.d.ts +0 -71
  423. package/dist/hooks/bash-risk.d.ts +0 -57
  424. package/dist/hooks/bash-risk.js +0 -137
  425. package/dist/hooks/edit-verification.d.ts +0 -62
  426. package/dist/hooks/edit-verification.js +0 -256
  427. package/dist/hooks/gate-block-helper.d.ts +0 -44
  428. package/dist/hooks/gate-block-helper.js +0 -119
  429. package/dist/hooks/gate.d.ts +0 -24
  430. package/dist/hooks/gate.js +0 -173
  431. package/dist/hooks/gfi-gate.d.ts +0 -40
  432. package/dist/hooks/gfi-gate.js +0 -113
  433. package/dist/hooks/lifecycle.d.ts +0 -5
  434. package/dist/hooks/lifecycle.js +0 -284
  435. package/dist/hooks/llm.d.ts +0 -13
  436. package/dist/hooks/message-sanitize.d.ts +0 -3
  437. package/dist/hooks/message-sanitize.js +0 -37
  438. package/dist/hooks/pain.d.ts +0 -5
  439. package/dist/hooks/pain.js +0 -301
  440. package/dist/hooks/progressive-trust-gate.d.ts +0 -52
  441. package/dist/hooks/progressive-trust-gate.js +0 -134
  442. package/dist/hooks/prompt.d.ts +0 -49
  443. package/dist/hooks/prompt.js +0 -905
  444. package/dist/hooks/subagent.d.ts +0 -10
  445. package/dist/hooks/subagent.js +0 -387
  446. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  447. package/dist/hooks/thinking-checkpoint.js +0 -51
  448. package/dist/hooks/trajectory-collector.d.ts +0 -32
  449. package/dist/hooks/trajectory-collector.js +0 -256
  450. package/dist/http/principles-console-route.d.ts +0 -9
  451. package/dist/http/principles-console-route.js +0 -681
  452. package/dist/i18n/commands.d.ts +0 -26
  453. package/dist/i18n/commands.js +0 -116
  454. package/dist/index.d.ts +0 -7
  455. package/dist/index.js +0 -581
  456. package/dist/service/central-database.d.ts +0 -104
  457. package/dist/service/central-database.js +0 -649
  458. package/dist/service/control-ui-query-service.d.ts +0 -221
  459. package/dist/service/control-ui-query-service.js +0 -543
  460. package/dist/service/empathy-observer-manager.d.ts +0 -88
  461. package/dist/service/empathy-observer-manager.js +0 -414
  462. package/dist/service/evolution-query-service.d.ts +0 -155
  463. package/dist/service/evolution-query-service.js +0 -258
  464. package/dist/service/evolution-worker.d.ts +0 -101
  465. package/dist/service/evolution-worker.js +0 -975
  466. package/dist/service/health-query-service.d.ts +0 -170
  467. package/dist/service/health-query-service.js +0 -662
  468. package/dist/service/nocturnal-runtime.d.ts +0 -183
  469. package/dist/service/nocturnal-service.d.ts +0 -163
  470. package/dist/service/nocturnal-service.js +0 -787
  471. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  472. package/dist/service/nocturnal-target-selector.js +0 -315
  473. package/dist/service/phase3-input-filter.d.ts +0 -73
  474. package/dist/service/phase3-input-filter.js +0 -172
  475. package/dist/service/runtime-summary-service.d.ts +0 -122
  476. package/dist/service/runtime-summary-service.js +0 -485
  477. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  478. package/dist/service/subagent-workflow/index.d.ts +0 -4
  479. package/dist/service/subagent-workflow/index.js +0 -3
  480. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  481. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  482. package/dist/service/subagent-workflow/types.js +0 -11
  483. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  484. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  485. package/dist/service/trajectory-service.d.ts +0 -2
  486. package/dist/service/trajectory-service.js +0 -15
  487. package/dist/tools/critique-prompt.d.ts +0 -14
  488. package/dist/tools/deep-reflect.d.ts +0 -39
  489. package/dist/tools/deep-reflect.js +0 -350
  490. package/dist/tools/model-index.d.ts +0 -9
  491. package/dist/types/event-types.d.ts +0 -306
  492. package/dist/types/event-types.js +0 -106
  493. package/dist/types/hygiene-types.d.ts +0 -20
  494. package/dist/types/hygiene-types.js +0 -12
  495. package/dist/types/runtime-summary.d.ts +0 -47
  496. package/dist/types/runtime-summary.js +0 -1
  497. package/dist/types.d.ts +0 -50
  498. package/dist/types.js +0 -22
  499. package/dist/utils/file-lock.d.ts +0 -71
  500. package/dist/utils/file-lock.js +0 -309
  501. package/dist/utils/glob-match.d.ts +0 -28
  502. package/dist/utils/hashing.d.ts +0 -9
  503. package/dist/utils/io.d.ts +0 -6
  504. package/dist/utils/io.js +0 -106
  505. package/dist/utils/nlp.d.ts +0 -9
  506. package/dist/utils/plugin-logger.d.ts +0 -39
  507. package/dist/utils/subagent-probe.d.ts +0 -34
  508. 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
+ });