principles-disciple 1.72.0 → 1.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  24. package/src/core/correction-cue-learner.ts +2 -136
  25. package/src/core/correction-types.ts +16 -88
  26. package/src/core/dictionary.ts +19 -20
  27. package/src/core/empathy-keyword-matcher.ts +17 -289
  28. package/src/core/empathy-types.ts +18 -229
  29. package/src/core/event-log.ts +38 -132
  30. package/src/core/evolution-reducer.ts +21 -2
  31. package/src/core/evolution-types.ts +76 -464
  32. package/src/core/file-store.ts +80 -0
  33. package/src/core/focus-history.ts +228 -955
  34. package/src/core/local-worker-routing.ts +34 -314
  35. package/src/core/merge-gate-audit.ts +0 -195
  36. package/src/core/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +459 -439
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +59 -16
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +3 -1
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. package/ui/src/utils/format.ts +0 -15
@@ -1,9 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { recommendInternalizationRoute } from '../../../src/core/principle-internalization/internalization-routing-policy.js';
2
+ import { recommendLifecycleRoute } from '@principles/core/runtime-v2';
3
3
  import type {
4
4
  PrincipleLifecycleEvidence,
5
5
  RuleLifecycleEvidence,
6
- } from '../../../src/core/principle-internalization/lifecycle-read-model.js';
6
+ } from '@principles/core/runtime-v2';
7
7
 
8
8
  function createRuleEvidence(id: string, overrides: Partial<RuleLifecycleEvidence> = {}): RuleLifecycleEvidence {
9
9
  return {
@@ -94,9 +94,9 @@ function createPrincipleEvidence(
94
94
  };
95
95
  }
96
96
 
97
- describe('internalization-routing-policy', () => {
97
+ describe('recommendLifecycleRoute', () => {
98
98
  it('recommends code for deterministic high-risk principles with repeated failures and sufficient replay evidence', () => {
99
- const recommendation = recommendInternalizationRoute(
99
+ const recommendation = recommendLifecycleRoute(
100
100
  createPrincipleEvidence(
101
101
  [
102
102
  createRuleEvidence('R-001', {
@@ -156,7 +156,7 @@ describe('internalization-routing-policy', () => {
156
156
  });
157
157
 
158
158
  it('recommends skill when a cheaper non-code path is viable', () => {
159
- const recommendation = recommendInternalizationRoute(
159
+ const recommendation = recommendLifecycleRoute(
160
160
  createPrincipleEvidence([
161
161
  createRuleEvidence('R-001', {
162
162
  rule: {
@@ -201,7 +201,7 @@ describe('internalization-routing-policy', () => {
201
201
  });
202
202
 
203
203
  it('recommends defer when evidence is too sparse to choose a route confidently', () => {
204
- const recommendation = recommendInternalizationRoute(
204
+ const recommendation = recommendLifecycleRoute(
205
205
  createPrincipleEvidence([createRuleEvidence('R-001')]),
206
206
  );
207
207
 
@@ -0,0 +1,56 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { FilesystemLifecycleDatasource, LineageSourceRetiredError } from '../../../src/core/principle-internalization/filesystem-lifecycle-datasource.js';
3
+ import { buildLifecycleReadModel } from '@principles/core/runtime-v2';
4
+
5
+ describe('LineageSourceRetiredError regression guard (PRI-230)', () => {
6
+ it('listLineageRecords throws LineageSourceRetiredError, not return []', () => {
7
+ const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
8
+ expect(() => ds.listLineageRecords('rule-implementation-candidate')).toThrow(LineageSourceRetiredError);
9
+ });
10
+
11
+ it('LineageSourceRetiredError message mentions PRI-230 and nocturnal-artifact-lineage', () => {
12
+ const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
13
+ try {
14
+ ds.listLineageRecords('rule-implementation-candidate');
15
+ expect.unreachable('Should have thrown');
16
+ } catch (err) {
17
+ expect(err).toBeInstanceOf(LineageSourceRetiredError);
18
+ const message = (err as LineageSourceRetiredError).message;
19
+ expect(message).toContain('PRI-230');
20
+ expect(message).toContain('nocturnal-artifact-lineage');
21
+ }
22
+ });
23
+
24
+ it('LineageSourceRetiredError has correct name property', () => {
25
+ const err = new LineageSourceRetiredError();
26
+ expect(err.name).toBe('LineageSourceRetiredError');
27
+ });
28
+
29
+ it('retired datasource does not return empty array that could be misinterpreted as "no lineage"', () => {
30
+ const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
31
+ let returnedEmptyArray = false;
32
+ try {
33
+ const result = ds.listLineageRecords('rule-implementation-candidate');
34
+ returnedEmptyArray = Array.isArray(result) && result.length === 0;
35
+ } catch {
36
+ // expected
37
+ }
38
+ expect(returnedEmptyArray).toBe(false);
39
+ });
40
+ });
41
+
42
+ describe('buildLifecycleReadModel with retired lineage source (PRI-230)', () => {
43
+ it('produces lineageEvidence with sourceRetired=true when datasource throws LineageSourceRetiredError', () => {
44
+ const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
45
+ let model: ReturnType<typeof buildLifecycleReadModel>;
46
+ expect(() => {
47
+ model = buildLifecycleReadModel(ds);
48
+ }).not.toThrow();
49
+
50
+ const rules = model!.principles.flatMap((p) => p.rules);
51
+ for (const rule of rules) {
52
+ expect(rule.lineageEvidence.sourceRetired).toBe(true);
53
+ expect(rule.lineageEvidence.records).toEqual([]);
54
+ }
55
+ });
56
+ });
@@ -3,7 +3,6 @@ import * as fs from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
5
  import { PrincipleLifecycleService } from '../../../src/core/principle-internalization/principle-lifecycle-service.js';
6
- import { appendCandidateArtifactLineageRecord, listArtifactLineageRecords } from '../../../src/core/nocturnal-artifact-lineage.js';
7
6
  import { getImplementationAssetRoot } from '../../../src/core/code-implementation-storage.js';
8
7
  import { loadLedger, saveLedger, type LedgerPrinciple, type LedgerRule } from '../../../src/core/principle-tree-ledger.js';
9
8
  import type { Implementation } from '../../../src/types/principle-tree-schema.js';
@@ -142,19 +141,6 @@ describe('principle-lifecycle-service', () => {
142
141
  JSON.stringify(makeReplayReport(), null, 2),
143
142
  'utf-8',
144
143
  );
145
-
146
- appendCandidateArtifactLineageRecord(workspaceDir, {
147
- artifactId: 'artifact-1',
148
- principleId: 'P-001',
149
- ruleId: 'R-001',
150
- sessionId: 'session-1',
151
- sourceSnapshotRef: 'snapshot-1',
152
- sourcePainIds: ['pain-1'],
153
- sourceGateBlockIds: ['gate-1'],
154
- storagePath: getImplementationAssetRoot(stateDir, 'IMPL-001'),
155
- implementationId: 'IMPL-001',
156
- createdAt: '2026-04-08T00:00:00.000Z',
157
- });
158
144
  }
159
145
 
160
146
  it('recomputes lifecycle metrics and exposes assessments plus route recommendations through one service', () => {
@@ -185,13 +171,12 @@ describe('principle-lifecycle-service', () => {
185
171
  expect(context.getActivePrincipleSubtrees()).toEqual([]);
186
172
  });
187
173
 
188
- it('keeps replay classifications behavioral-only and preserves candidate lineage semantics', () => {
174
+ it('keeps replay classifications behavioral-only', () => {
189
175
  seedWorkspace();
190
176
  const service = new PrincipleLifecycleService(workspaceDir, stateDir);
191
177
 
192
178
  const assessments = service.listAssessments();
193
179
  const readModel = service.buildReadModel();
194
- const lineage = listArtifactLineageRecords(workspaceDir, 'rule-implementation-candidate');
195
180
  const latestReport = readModel.principles[0].rules[0].replayEvidence.latestReports[0];
196
181
 
197
182
  expect(assessments[0].routeRecommendation.route).toBe('skill');
@@ -200,12 +185,5 @@ describe('principle-lifecycle-service', () => {
200
185
  'successPositive',
201
186
  'principleAnchor',
202
187
  ]);
203
- expect(lineage).toHaveLength(1);
204
- expect(lineage[0]).toMatchObject({
205
- artifactKind: 'rule-implementation-candidate',
206
- sourcePainIds: ['pain-1'],
207
- sourceGateBlockIds: ['gate-1'],
208
- implementationId: 'IMPL-001',
209
- });
210
188
  });
211
189
  });
@@ -0,0 +1,253 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import { PrincipleTreeLedgerAdapter } from '../../src/core/principle-tree-ledger-adapter.js';
6
+ import { loadLedger } from '../../src/core/principle-tree-ledger.js';
7
+ import { CandidateIntakeError, INTAKE_ERROR_CODES } from '@principles/core/runtime-v2';
8
+ import type { LedgerPrincipleEntry } from '@principles/core/runtime-v2';
9
+ import { safeRmDir } from '../test-utils.js';
10
+
11
+ // Mock principle-tree-ledger.js module
12
+ // Use vi.mock with factory to provide controllable addPrincipleToLedger
13
+ vi.mock('../../src/core/principle-tree-ledger.js', async () => {
14
+ const actual = await vi.importActual<typeof import('../../src/core/principle-tree-ledger.js')>('../../src/core/principle-tree-ledger.js');
15
+ return {
16
+ ...actual,
17
+ addPrincipleToLedger: vi.fn(actual.addPrincipleToLedger),
18
+ };
19
+ });
20
+
21
+ describe('PrincipleTreeLedgerAdapter', () => {
22
+ let tempDir: string;
23
+ let stateDir: string;
24
+
25
+ beforeEach(() => {
26
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-adapter-'));
27
+ stateDir = path.join(tempDir, '.state');
28
+ fs.mkdirSync(stateDir, { recursive: true });
29
+ });
30
+
31
+ afterEach(() => {
32
+ safeRmDir(tempDir);
33
+ });
34
+
35
+ function createEntry(overrides: Partial<LedgerPrincipleEntry> = {}): LedgerPrincipleEntry {
36
+ return {
37
+ id: '550e8400-e29b-41d4-a716-446655440000',
38
+ title: 'Test Principle',
39
+ text: 'When file delete is attempted, then verify backup exists first.',
40
+ triggerPattern: 'file delete',
41
+ action: 'verify backup exists',
42
+ status: 'probation',
43
+ evaluability: 'weak_heuristic',
44
+ sourceRef: 'candidate://test-candidate-001',
45
+ artifactRef: 'artifact://test-artifact-001',
46
+ taskRef: 'task://test-task-001',
47
+ createdAt: '2026-04-26T10:00:00.000Z',
48
+ ...overrides,
49
+ };
50
+ }
51
+
52
+ describe('writeProbationEntry', () => {
53
+ it('writes a LedgerPrinciple to the ledger file with correct field expansion', () => {
54
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
55
+ const entry = createEntry();
56
+ const result = adapter.writeProbationEntry(entry);
57
+
58
+ expect(result).toBe(entry);
59
+
60
+ const store = loadLedger(stateDir);
61
+ const written = store.tree.principles[entry.id];
62
+ expect(written).toBeDefined();
63
+ expect(written.id).toBe(entry.id);
64
+ expect(written.text).toBe(entry.text);
65
+ expect(written.status).toBe('candidate');
66
+ expect(written.triggerPattern).toBe(entry.triggerPattern);
67
+ expect(written.action).toBe(entry.action);
68
+ });
69
+
70
+ it('is idempotent — second call with same candidateId returns existing entry, no double write', () => {
71
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
72
+ const entry = createEntry();
73
+ const first = adapter.writeProbationEntry(entry);
74
+ const second = adapter.writeProbationEntry(entry);
75
+
76
+ expect(second).toBe(first);
77
+
78
+ const store = loadLedger(stateDir);
79
+ expect(Object.keys(store.tree.principles).length).toBe(1);
80
+ });
81
+
82
+ it('different candidates produce different entries', () => {
83
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
84
+ const entry1 = createEntry({
85
+ id: '550e8400-e29b-41d4-a716-446655440000',
86
+ sourceRef: 'candidate://alpha',
87
+ });
88
+ const entry2 = createEntry({
89
+ id: '660e8400-e29b-41d4-a716-446655440001',
90
+ sourceRef: 'candidate://beta',
91
+ title: 'Another Principle',
92
+ text: 'When network fails, retry with backoff.',
93
+ });
94
+
95
+ adapter.writeProbationEntry(entry1);
96
+ adapter.writeProbationEntry(entry2);
97
+
98
+ const store = loadLedger(stateDir);
99
+ expect(Object.keys(store.tree.principles).length).toBe(2);
100
+ expect(store.tree.principles[entry1.id]).toBeDefined();
101
+ expect(store.tree.principles[entry2.id]).toBeDefined();
102
+
103
+ expect(adapter.existsForCandidate('alpha')).toBeDefined();
104
+ expect(adapter.existsForCandidate('beta')).toBeDefined();
105
+ });
106
+ });
107
+
108
+ describe('existsForCandidate', () => {
109
+ it('returns the entry for a previously written candidate', () => {
110
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
111
+ const entry = createEntry();
112
+ adapter.writeProbationEntry(entry);
113
+
114
+ const found = adapter.existsForCandidate('test-candidate-001');
115
+ expect(found).not.toBeNull();
116
+ expect(found?.id).toBe(entry.id);
117
+ expect(found?.title).toBe(entry.title);
118
+ expect(found?.sourceRef).toBe(entry.sourceRef);
119
+ });
120
+
121
+ it('returns null for an unknown candidate', () => {
122
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
123
+ const result = adapter.existsForCandidate('nonexistent-candidate');
124
+ expect(result).toBeNull();
125
+ });
126
+ });
127
+
128
+ describe('field expansion', () => {
129
+ it('maps status probation to candidate', () => {
130
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
131
+ const entry = createEntry();
132
+ adapter.writeProbationEntry(entry);
133
+
134
+ const store = loadLedger(stateDir);
135
+ const written = store.tree.principles[entry.id];
136
+ expect(written.status).toBe('candidate');
137
+ expect(written.status).not.toBe('probation');
138
+ });
139
+
140
+ it('applies default values correctly', () => {
141
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
142
+ const entry = createEntry({
143
+ triggerPattern: undefined,
144
+ action: undefined,
145
+ artifactRef: undefined,
146
+ taskRef: undefined,
147
+ });
148
+ adapter.writeProbationEntry(entry);
149
+
150
+ const store = loadLedger(stateDir);
151
+ const written = store.tree.principles[entry.id];
152
+ expect(written.version).toBe(1);
153
+ expect(written.priority).toBe('P1');
154
+ expect(written.scope).toBe('general');
155
+ expect(written.valueScore).toBe(0);
156
+ expect(written.adherenceRate).toBe(0);
157
+ expect(written.painPreventedCount).toBe(0);
158
+ expect(written.createdAt).toBe(entry.createdAt);
159
+ expect(written.updatedAt).toBe(entry.createdAt);
160
+ });
161
+
162
+ it('passes through triggerPattern and action (Decision B)', () => {
163
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
164
+ const entry1 = createEntry({ triggerPattern: 'file delete', action: 'verify backup' });
165
+ adapter.writeProbationEntry(entry1);
166
+
167
+ let store = loadLedger(stateDir);
168
+ let written = store.tree.principles[entry1.id];
169
+ expect(written.triggerPattern).toBe('file delete');
170
+ expect(written.action).toBe('verify backup');
171
+
172
+ const entry2 = createEntry({
173
+ id: '770e8400-e29b-41d4-a716-446655440002',
174
+ sourceRef: 'candidate://test-002',
175
+ triggerPattern: undefined,
176
+ action: undefined,
177
+ });
178
+ adapter.writeProbationEntry(entry2);
179
+
180
+ store = loadLedger(stateDir);
181
+ written = store.tree.principles[entry2.id];
182
+ expect(written.triggerPattern).toBe('');
183
+ expect(written.action).toBe('');
184
+ });
185
+
186
+ it('does NOT include sourceRef, artifactRef, taskRef in ledger (Decision C)', () => {
187
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
188
+ const entry = createEntry();
189
+ adapter.writeProbationEntry(entry);
190
+
191
+ const store = loadLedger(stateDir);
192
+ const written = store.tree.principles[entry.id];
193
+ expect('sourceRef' in written).toBe(false);
194
+ expect('artifactRef' in written).toBe(false);
195
+ expect('taskRef' in written).toBe(false);
196
+
197
+ // But existsForCandidate still works
198
+ expect(adapter.existsForCandidate('test-candidate-001')).not.toBeNull();
199
+ });
200
+
201
+ it('populates derivedFromPainIds with candidateId (Q1 resolved)', () => {
202
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
203
+ const entry = createEntry({ sourceRef: 'candidate://test-candidate-001' });
204
+ adapter.writeProbationEntry(entry);
205
+
206
+ const store = loadLedger(stateDir);
207
+ const written = store.tree.principles[entry.id];
208
+ expect(written.derivedFromPainIds).toEqual(['test-candidate-001']);
209
+ });
210
+ });
211
+
212
+ describe('error handling', () => {
213
+ it('throws LEDGER_WRITE_FAILED when addPrincipleToLedger fails', async () => {
214
+ const mockedModule = await import('../../src/core/principle-tree-ledger.js');
215
+ const mockAdd = vi.mocked(mockedModule.addPrincipleToLedger);
216
+ mockAdd.mockImplementation(() => {
217
+ throw new Error('Disk full');
218
+ });
219
+
220
+ const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
221
+ const entry = createEntry();
222
+
223
+ expect(() => adapter.writeProbationEntry(entry)).toThrow(CandidateIntakeError);
224
+ try {
225
+ adapter.writeProbationEntry(entry);
226
+ } catch (err) {
227
+ expect(err).toBeInstanceOf(CandidateIntakeError);
228
+ expect((err as CandidateIntakeError).code).toBe(INTAKE_ERROR_CODES.LEDGER_WRITE_FAILED);
229
+ expect((err as Error).message).toContain(entry.id);
230
+ }
231
+
232
+ mockAdd.mockRestore();
233
+ });
234
+ });
235
+
236
+ describe('instance isolation', () => {
237
+ it('separate instances have separate Maps', () => {
238
+ const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-adapter-'));
239
+ const stateDir2 = path.join(tempDir2, '.state');
240
+ fs.mkdirSync(stateDir2, { recursive: true });
241
+
242
+ const adapterA = new PrincipleTreeLedgerAdapter({ stateDir });
243
+ const adapterB = new PrincipleTreeLedgerAdapter({ stateDir: stateDir2 });
244
+
245
+ const entryA = createEntry({ sourceRef: 'candidate://candidate-A' });
246
+ adapterA.writeProbationEntry(entryA);
247
+
248
+ expect(adapterB.existsForCandidate('candidate-A')).toBeNull();
249
+
250
+ safeRmDir(tempDir2);
251
+ });
252
+ });
253
+ });
@@ -139,8 +139,6 @@ describe('ReflectionContextCollector', () => {
139
139
  expect(result!.principle.id).toBe('P_001');
140
140
  expect(result!.painEvents).toEqual([]);
141
141
  expect(result!.sessionSnapshot).toBeNull();
142
- expect(result!.lineage.sourcePainIds).toEqual(['pain_no_exist']);
143
- expect(result!.lineage.sessionId).toBeNull();
144
142
  });
145
143
 
146
144
  it('returns context with pain events and null snapshot when painIds match events but not a specific session', () => {
@@ -159,9 +157,6 @@ describe('ReflectionContextCollector', () => {
159
157
 
160
158
  expect(result).not.toBeNull();
161
159
  expect(result!.principle.id).toBe('P_002');
162
- expect(result!.lineage.sourcePainIds).toEqual(['pain_001']);
163
- // painId -> sessionId resolution is a known gap, so painEvents may be empty
164
- // and sessionSnapshot may be null
165
160
  });
166
161
 
167
162
  it('resolves pain events from a known session', () => {
@@ -181,9 +176,6 @@ describe('ReflectionContextCollector', () => {
181
176
 
182
177
  expect(result).not.toBeNull();
183
178
  expect(result!.principle.id).toBe('P_003');
184
- // painId -> sessionId gap: we attempt to find sessions containing pain events
185
- // The lineage sessionId should be populated if we can resolve it
186
- expect(result!.lineage.sourcePainIds).toEqual(['pain_from_sess_with_pain']);
187
179
  });
188
180
  });
189
181
 
@@ -258,8 +250,6 @@ describe('ReflectionContextCollector', () => {
258
250
  expect(results).toHaveLength(1);
259
251
 
260
252
  const ctx = results[0];
261
- expect(ctx.lineage.sourcePainIds).toEqual(['p1', 'p2', 'p3']);
262
- expect(ctx.lineage.sessionId).toBeNull();
263
253
  expect(ctx.sessionSnapshot).toBeNull();
264
254
  });
265
255
  });
@@ -297,7 +287,6 @@ describe('ReflectionContextCollector', () => {
297
287
  // Exact match: only ONE pain event should match
298
288
  expect(result!.painEvents).toHaveLength(1);
299
289
  expect(result!.painEvents[0].reason).toBe('exact_match_target');
300
- expect(result!.lineage.sessionId).toBe(sessionId);
301
290
  });
302
291
 
303
292
  it('falls back to substring heuristic when no exact ID match', () => {
@@ -340,17 +329,14 @@ describe('ReflectionContextCollector', () => {
340
329
  expect(results).toHaveLength(1);
341
330
 
342
331
  const ctx: ReflectionContext = results[0];
343
- // Verify all fields exist
344
332
  expect(ctx).toHaveProperty('principle');
345
333
  expect(ctx).toHaveProperty('painEvents');
346
334
  expect(ctx).toHaveProperty('sessionSnapshot');
347
- expect(ctx).toHaveProperty('lineage');
348
335
 
349
336
  expect(ctx.principle.id).toBe('P_SHAPE');
350
337
  expect(ctx.principle.text).toBe('Shape test principle');
351
338
  expect(ctx.principle.action).toBe('Do the thing');
352
339
  expect(Array.isArray(ctx.painEvents)).toBe(true);
353
- expect(ctx.lineage.sourcePainIds).toEqual(['pain_shape']);
354
340
  });
355
341
  });
356
342
  });