principles-disciple 1.72.0 → 1.74.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 (319) hide show
  1. package/INSTALL.md +1 -3
  2. package/openclaw.plugin.json +10 -5
  3. package/package.json +17 -19
  4. package/scripts/acceptance-test.mjs +16 -73
  5. package/scripts/sync-plugin.mjs +382 -77
  6. package/src/commands/archive-impl.ts +2 -1
  7. package/src/commands/capabilities.ts +2 -2
  8. package/src/commands/context.ts +2 -2
  9. package/src/commands/disable-impl.ts +2 -1
  10. package/src/commands/evolution-status.ts +16 -16
  11. package/src/commands/export.ts +12 -67
  12. package/src/commands/pain.ts +91 -1
  13. package/src/commands/principle-rollback.ts +2 -1
  14. package/src/commands/promote-impl.ts +7 -43
  15. package/src/commands/rollback-impl.ts +2 -1
  16. package/src/commands/rollback.ts +2 -1
  17. package/src/commands/samples.ts +2 -1
  18. package/src/commands/thinking-os.ts +2 -1
  19. package/src/config/errors.ts +18 -2
  20. package/src/constants/diagnostician.ts +2 -2
  21. package/src/constants/tools.ts +2 -1
  22. package/src/core/__tests__/focus-history.test.ts +210 -0
  23. package/src/core/config.ts +1 -1
  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 +29 -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/migration.ts +0 -1
  37. package/src/core/pain-diagnostic-gate.ts +154 -0
  38. package/src/core/pain-signal.ts +21 -138
  39. package/src/core/pain.ts +15 -88
  40. package/src/core/path-resolver.ts +0 -1
  41. package/src/core/paths.ts +0 -1
  42. package/src/core/pd-task-reconciler.ts +26 -115
  43. package/src/core/pd-task-service.ts +9 -9
  44. package/src/core/pd-task-types.ts +23 -127
  45. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  46. package/src/core/principle-compiler/code-validator.ts +15 -42
  47. package/src/core/principle-compiler/compiler.ts +100 -15
  48. package/src/core/principle-compiler/index.ts +5 -2
  49. package/src/core/principle-compiler/template-generator.ts +4 -104
  50. package/src/core/principle-injection.ts +10 -202
  51. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  52. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  53. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  54. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  55. package/src/core/principle-tree-ledger.ts +8 -6
  56. package/src/core/reflection/reflection-context.ts +14 -109
  57. package/src/core/replay-engine.ts +8 -500
  58. package/src/core/rule-host-helpers.ts +5 -35
  59. package/src/core/rule-host-types.ts +10 -82
  60. package/src/core/rule-host.ts +6 -63
  61. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  62. package/src/core/session-tracker.ts +87 -101
  63. package/src/core/shadow-observation-registry.ts +19 -48
  64. package/src/core/trajectory.ts +3 -1
  65. package/src/core/workflow-funnel-loader.ts +62 -68
  66. package/src/core/workspace-context.ts +46 -0
  67. package/src/core/workspace-dir-service.ts +1 -1
  68. package/src/core/workspace-dir-validation.ts +18 -9
  69. package/src/hooks/AGENTS.md +1 -1
  70. package/src/hooks/gate-block-helper.ts +71 -64
  71. package/src/hooks/gate.ts +183 -31
  72. package/src/hooks/lifecycle.ts +30 -32
  73. package/src/hooks/llm.ts +60 -32
  74. package/src/hooks/pain.ts +297 -103
  75. package/src/hooks/prompt.ts +400 -440
  76. package/src/hooks/subagent.ts +2 -29
  77. package/src/i18n/commands.ts +2 -10
  78. package/src/index.ts +95 -85
  79. package/src/openclaw-sdk.ts +311 -0
  80. package/src/service/central-database.ts +8 -4
  81. package/src/service/evolution-queue-migration.ts +2 -1
  82. package/src/service/evolution-worker.ts +163 -1786
  83. package/src/service/internalization-trigger-adapter.ts +302 -0
  84. package/src/service/keyword-optimization-service.ts +4 -4
  85. package/src/service/monitoring-query-service.ts +1 -215
  86. package/src/service/queue-io.ts +60 -331
  87. package/src/service/runtime-summary-service.ts +59 -16
  88. package/src/service/subagent-workflow/index.ts +0 -41
  89. package/src/service/subagent-workflow/types.ts +9 -120
  90. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  91. package/src/service/workflow-watchdog.ts +0 -43
  92. package/src/types/event-payload.ts +16 -74
  93. package/src/types/event-types.ts +38 -547
  94. package/src/types/hygiene-types.ts +7 -30
  95. package/src/types/principle-tree-schema.ts +20 -222
  96. package/src/types/queue.ts +15 -70
  97. package/src/types/runtime-summary.ts +5 -49
  98. package/src/utils/io.ts +8 -20
  99. package/src/utils/retry.ts +1 -1
  100. package/src/utils/shadow-fingerprint.ts +2 -2
  101. package/src/utils/workspace-resolver.ts +50 -0
  102. package/templates/langs/en/core/AGENTS.md +7 -7
  103. package/templates/langs/en/core/BOOT.md +1 -1
  104. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  105. package/templates/langs/en/principles/THINKING_OS.md +3 -2
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  107. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  108. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  109. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  110. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  111. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  112. package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
  113. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  114. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  115. package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
  116. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  117. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  118. package/templates/langs/zh/core/AGENTS.md +7 -7
  119. package/templates/langs/zh/core/BOOT.md +1 -1
  120. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  121. package/templates/langs/zh/principles/THINKING_OS.md +3 -2
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  127. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  128. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  129. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  130. package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
  131. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  132. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  133. package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
  134. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  135. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  136. package/tests/build-artifacts.test.ts +1 -3
  137. package/tests/commands/evolution-status.test.ts +0 -118
  138. package/tests/core/bootstrap-rules.test.ts +1 -1
  139. package/tests/core/config.test.ts +1 -1
  140. package/tests/core/event-log.test.ts +35 -0
  141. package/tests/core/evolution-engine.test.ts +610 -0
  142. package/tests/core/file-store.test.ts +102 -0
  143. package/tests/core/focus-history.test.ts +203 -11
  144. package/tests/core/merge-gate-audit.test.ts +2 -169
  145. package/tests/core/migration.test.ts +7 -7
  146. package/tests/core/model-deployment-registry.test.ts +7 -1
  147. package/tests/core/model-training-registry.test.ts +19 -0
  148. package/tests/core/observability.test.ts +0 -1
  149. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  150. package/tests/core/pain.test.ts +0 -1
  151. package/tests/core/path-resolver.test.ts +1 -1
  152. package/tests/core/paths-refactor.test.ts +0 -22
  153. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  154. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  155. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  156. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  157. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  158. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  159. package/tests/core/reflection-context.test.ts +0 -14
  160. package/tests/core/replay-engine.test.ts +127 -215
  161. package/tests/core/rule-host-helpers.test.ts +2 -2
  162. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  163. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  164. package/tests/core/workspace-context.test.ts +2 -2
  165. package/tests/core/workspace-dir-validation.test.ts +8 -1
  166. package/tests/core-anti-growth.test.ts +191 -0
  167. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  168. package/tests/hooks/confirm-first-removal.test.ts +188 -0
  169. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  170. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  171. package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
  172. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  173. package/tests/hooks/pain.test.ts +269 -12
  174. package/tests/hooks/prompt-characterization.test.ts +500 -0
  175. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  176. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  177. package/tests/index.test.ts +94 -1
  178. package/tests/integration/auto-entry-gate.test.ts +248 -0
  179. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  180. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  181. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  182. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  183. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  184. package/tests/service/central-database.test.ts +457 -0
  185. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  186. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  187. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  188. package/tests/service/monitoring-query-service.test.ts +1 -47
  189. package/tests/service/queue-io.test.ts +1 -62
  190. package/tests/service/runtime-summary-service.test.ts +3 -1
  191. package/tests/service/workflow-watchdog.test.ts +0 -91
  192. package/tests/utils/file-lock.test.ts +5 -3
  193. package/tests/utils/session-key.test.ts +52 -0
  194. package/tests/utils/subagent-probe.test.ts +48 -1
  195. package/vitest.config.ts +4 -11
  196. package/.planning/codebase/ARCHITECTURE.md +0 -157
  197. package/.planning/codebase/CONCERNS.md +0 -145
  198. package/.planning/codebase/CONVENTIONS.md +0 -148
  199. package/.planning/codebase/INTEGRATIONS.md +0 -81
  200. package/.planning/codebase/STACK.md +0 -87
  201. package/.planning/codebase/STRUCTURE.md +0 -193
  202. package/.planning/codebase/TESTING.md +0 -243
  203. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  204. package/docs/COMMAND_REFERENCE.md +0 -76
  205. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  206. package/scripts/build-web.mjs +0 -46
  207. package/scripts/diagnose-nocturnal.mjs +0 -537
  208. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  209. package/src/commands/nocturnal-review.ts +0 -322
  210. package/src/commands/nocturnal-rollout.ts +0 -790
  211. package/src/commands/nocturnal-train.ts +0 -986
  212. package/src/commands/pd-reflect.ts +0 -88
  213. package/src/core/adaptive-thresholds.ts +0 -478
  214. package/src/core/diagnostician-task-store.ts +0 -192
  215. package/src/core/nocturnal-arbiter.ts +0 -715
  216. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  217. package/src/core/nocturnal-artificer.ts +0 -257
  218. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  219. package/src/core/nocturnal-compliance.ts +0 -1146
  220. package/src/core/nocturnal-dataset.ts +0 -763
  221. package/src/core/nocturnal-executability.ts +0 -428
  222. package/src/core/nocturnal-export.ts +0 -499
  223. package/src/core/nocturnal-paths.ts +0 -240
  224. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  225. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  226. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  227. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  228. package/src/core/nocturnal-trinity-types.ts +0 -218
  229. package/src/core/nocturnal-trinity.ts +0 -2680
  230. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  231. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  232. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  233. package/src/http/principles-console-route.ts +0 -709
  234. package/src/service/central-health-service.ts +0 -49
  235. package/src/service/central-overview-service.ts +0 -138
  236. package/src/service/control-ui-query-service.ts +0 -900
  237. package/src/service/cooldown-strategy.ts +0 -97
  238. package/src/service/evolution-pain-context.ts +0 -79
  239. package/src/service/evolution-query-service.ts +0 -407
  240. package/src/service/health-query-service.ts +0 -1038
  241. package/src/service/nocturnal-config.ts +0 -214
  242. package/src/service/nocturnal-runtime.ts +0 -734
  243. package/src/service/nocturnal-service.ts +0 -1605
  244. package/src/service/nocturnal-target-selector.ts +0 -545
  245. package/src/service/sleep-cycle.ts +0 -157
  246. package/src/service/startup-reconciler.ts +0 -112
  247. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  248. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  249. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  250. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  251. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  252. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  253. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  254. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  255. package/src/tools/write-pain-flag.ts +0 -215
  256. package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
  257. package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
  258. package/tests/commands/nocturnal-review.test.ts +0 -448
  259. package/tests/commands/nocturnal-train.test.ts +0 -97
  260. package/tests/commands/pd-reflect.test.ts +0 -49
  261. package/tests/core/adaptive-thresholds.test.ts +0 -261
  262. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  263. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  264. package/tests/core/nocturnal-artificer.test.ts +0 -241
  265. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  266. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  267. package/tests/core/nocturnal-compliance.test.ts +0 -646
  268. package/tests/core/nocturnal-dataset.test.ts +0 -892
  269. package/tests/core/nocturnal-e2e.test.ts +0 -234
  270. package/tests/core/nocturnal-executability.test.ts +0 -357
  271. package/tests/core/nocturnal-export.test.ts +0 -517
  272. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  273. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  274. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  275. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  276. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  277. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  278. package/tests/core/pain-auto-repair.test.ts +0 -96
  279. package/tests/core/pain-integration.test.ts +0 -510
  280. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  281. package/tests/http/principles-console-route.test.ts +0 -162
  282. package/tests/integration/chaos-resilience.test.ts +0 -348
  283. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  284. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  285. package/tests/service/control-ui-query-service.test.ts +0 -121
  286. package/tests/service/cooldown-strategy.test.ts +0 -164
  287. package/tests/service/data-endpoints-regression.test.ts +0 -834
  288. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  289. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  290. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  291. package/tests/service/nocturnal-runtime.test.ts +0 -473
  292. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  293. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  294. package/tests/service/startup-reconciler.test.ts +0 -148
  295. package/tests/tools/write-pain-flag.test.ts +0 -358
  296. package/ui/src/App.tsx +0 -45
  297. package/ui/src/api.ts +0 -220
  298. package/ui/src/charts.tsx +0 -955
  299. package/ui/src/components/ErrorState.tsx +0 -6
  300. package/ui/src/components/Loading.tsx +0 -13
  301. package/ui/src/components/ProtectedRoute.tsx +0 -12
  302. package/ui/src/components/Shell.tsx +0 -91
  303. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  304. package/ui/src/components/index.ts +0 -5
  305. package/ui/src/context/auth.tsx +0 -80
  306. package/ui/src/context/theme.tsx +0 -66
  307. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  308. package/ui/src/i18n/ui.ts +0 -473
  309. package/ui/src/main.tsx +0 -16
  310. package/ui/src/pages/EvolutionPage.tsx +0 -333
  311. package/ui/src/pages/FeedbackPage.tsx +0 -138
  312. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  313. package/ui/src/pages/LoginPage.tsx +0 -89
  314. package/ui/src/pages/OverviewPage.tsx +0 -599
  315. package/ui/src/pages/SamplesPage.tsx +0 -174
  316. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  317. package/ui/src/styles.css +0 -2020
  318. package/ui/src/types.ts +0 -384
  319. package/ui/src/utils/format.ts +0 -15
@@ -1,715 +0,0 @@
1
- /**
2
- * Nocturnal Arbiter — Deterministic Validation of Reflection Artifacts
3
- * ===================================================================
4
- *
5
- * PURPOSE: Validate that a reflection artifact passes all deterministic checks
6
- * before being approved for persistence. This module is PURE FUNCTIONS —
7
- * no side effects, no file I/O.
8
- *
9
- * VALIDATION RULES:
10
- * 1. JSON is parseable and has required fields
11
- * 2. principleId matches the target principle
12
- * 3. sessionId matches the source snapshot
13
- * 4. All required string fields are non-empty
14
- * 5. No fields contain placeholder or dummy values
15
- * 6. artifactId is a valid unique identifier
16
- * 7. No raw/private content in any text field
17
- *
18
- * DESIGN CONSTRAINTS:
19
- * - Pure functions only — no I/O, no side effects
20
- * - Deterministic — same input always produces same output
21
- * - Fail closed — invalid artifacts are rejected, never sanitized
22
- * - No LLM involvement — all checks are algorithmic
23
- */
24
-
25
- // ---------------------------------------------------------------------------
26
- // Types
27
- // ---------------------------------------------------------------------------
28
-
29
- /**
30
- * A raw reflection artifact as generated by the reflector.
31
- * This is the raw output before arbiter validation.
32
- */
33
- export interface RawReflectionArtifact {
34
- artifactId?: unknown;
35
- sessionId?: unknown;
36
- principleId?: unknown;
37
- sourceSnapshotRef?: unknown;
38
- badDecision?: unknown;
39
- betterDecision?: unknown;
40
- rationale?: unknown;
41
- createdAt?: unknown;
42
- thinkingModelDelta?: unknown;
43
- planningRatioGain?: unknown;
44
- invalid?: unknown;
45
- reason?: unknown;
46
- }
47
-
48
- /**
49
- * A validated and approved reflection artifact.
50
- * This is the output after arbiter validation passes.
51
- */
52
- export interface NocturnalArtifact {
53
- artifactId: string;
54
- sessionId: string;
55
- principleId: string;
56
- sourceSnapshotRef: string;
57
- badDecision: string;
58
- betterDecision: string;
59
- rationale: string;
60
- createdAt: string;
61
- /** Design-alignment metric: delta in thinking model activation (-1 to 1) */
62
- thinkingModelDelta?: number;
63
- /** Design-alignment metric: gain in planning ratio (-1 to 1) */
64
- planningRatioGain?: number;
65
- }
66
-
67
- /**
68
- * Validation failure reason.
69
- */
70
- export interface ArbiterFailure {
71
- reason: string;
72
- field?: string;
73
- }
74
-
75
- /**
76
- * Result of arbiter validation.
77
- */
78
- export interface ArbiterResult {
79
- /** Whether the artifact passed validation */
80
- passed: boolean;
81
- /** The validated artifact (if passed) */
82
- artifact?: NocturnalArtifact;
83
- /** The raw input (if failed) */
84
- rawInput?: RawReflectionArtifact;
85
- /** Failure reasons (if failed) */
86
- failures: ArbiterFailure[];
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Validation Helpers
91
- // ---------------------------------------------------------------------------
92
-
93
- function isNonEmptyString(val: unknown): val is string {
94
- return typeof val === 'string' && val.trim().length > 0;
95
- }
96
-
97
- function isValidUUID(val: string): boolean {
98
- // Simple UUID v4 pattern check
99
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(val);
100
- }
101
-
102
- function isISO8601(val: string): boolean {
103
- // ISO 8601 timestamp check
104
- return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(val);
105
- }
106
-
107
- /**
108
- * Check if a string contains placeholder/dummy values.
109
- */
110
- function containsPlaceholder(val: string): boolean {
111
- const placeholders = [
112
- '<placeholder>',
113
- '<uuid>',
114
- '<session-id>',
115
- '<principle-id>',
116
- '<reason>',
117
- '<action>',
118
- 'undefined',
119
- 'null',
120
- 'n/a',
121
- 'tbd',
122
- 'todo',
123
- 'fixme',
124
- ];
125
- const lower = val.toLowerCase();
126
- return placeholders.some((p) => lower === p || lower.startsWith(p + ' '));
127
- }
128
-
129
- /**
130
- * Check if a string contains raw/private content patterns.
131
- * Only matches actual code syntax or credential literals — not
132
- * incidental words that happen to appear in code context.
133
- */
134
- function containsRawContent(val: string): boolean {
135
- // Actual code syntax — these are unambiguous and won't appear in normal decision text
136
- const codePatterns = [
137
- /function\s+\w+\s*\(/, // function definitions: "function foo("
138
- /class\s+\w+/, // class definitions: "class Foo"
139
- /const\s+\w+\s*=/, // const declarations: "const x ="
140
- /let\s+\w+\s*=/, // let declarations: "let x ="
141
- /import\s+.+\s+from/, // import statements: "import ... from"
142
- /export\s+(default\s+)?/, // export statements
143
- /\bapi[_-]?key\s*=\s*\S+/i, // api_key= or api-key= with a value
144
- /\bpassword\s*=\s*\S+/i, // password= with a value
145
- /\bsecret\s*=\s*\S+/i, // secret= with a value
146
- /\btoken\s*=\s*\S+/i, // token= with a value
147
- ];
148
- return codePatterns.some((p) => p.test(val));
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Trinity Intermediate Validation
153
- // ---------------------------------------------------------------------------
154
-
155
- /**
156
- * Validation result for a Trinity stage output.
157
- */
158
- export interface TrinityStageValidationResult {
159
- valid: boolean;
160
- failures: string[];
161
- }
162
-
163
- /**
164
- * Validate a Dreamer output contract.
165
- * Ensures the output is well-formed before passing to Philosopher.
166
- */
167
-
168
- export function validateDreamerOutput(output: unknown): TrinityStageValidationResult {
169
- const failures: string[] = [];
170
-
171
- if (output === null || output === undefined || typeof output !== 'object') {
172
- return { valid: false, failures: ['Dreamer output must be a JSON object'] };
173
- }
174
-
175
- const obj = output as Record<string, unknown>;
176
-
177
- // Check valid flag
178
- if (obj.valid !== true) {
179
- if (typeof obj.reason === 'string' && obj.reason.length > 0) {
180
- failures.push(`Dreamer marked invalid: ${obj.reason}`);
181
- } else {
182
- failures.push('Dreamer output marked invalid with no reason');
183
- }
184
- }
185
-
186
- // Check candidates array
187
- if (!Array.isArray(obj.candidates)) {
188
- failures.push('Dreamer output must have a candidates array');
189
- } else {
190
- // Validate each candidate
191
- obj.candidates.forEach((candidate: unknown, idx: number) => {
192
- if (candidate === null || candidate === undefined || typeof candidate !== 'object') {
193
- failures.push(`Dreamer candidate at index ${idx} is not an object`);
194
- return;
195
- }
196
- const c = candidate as Record<string, unknown>;
197
-
198
- if (typeof c.candidateIndex !== 'number') {
199
- failures.push(`Dreamer candidate ${idx} missing candidateIndex`);
200
- }
201
- if (typeof c.badDecision !== 'string' || c.badDecision.trim().length === 0) {
202
- failures.push(`Dreamer candidate ${idx} missing non-empty badDecision`);
203
- }
204
- if (typeof c.betterDecision !== 'string' || c.betterDecision.trim().length === 0) {
205
- failures.push(`Dreamer candidate ${idx} missing non-empty betterDecision`);
206
- }
207
- if (typeof c.rationale !== 'string' || c.rationale.trim().length === 0) {
208
- failures.push(`Dreamer candidate ${idx} missing non-empty rationale`);
209
- }
210
- if (typeof c.confidence !== 'number' || c.confidence < 0 || c.confidence > 1) {
211
- failures.push(`Dreamer candidate ${idx} has invalid confidence (must be 0-1)`);
212
- }
213
-
214
- // badDecision and betterDecision should not be identical
215
- if (
216
- typeof c.badDecision === 'string' &&
217
- typeof c.betterDecision === 'string' &&
218
- c.badDecision.trim() === c.betterDecision.trim()
219
- ) {
220
- failures.push(`Dreamer candidate ${idx}: badDecision and betterDecision are identical`);
221
- }
222
- });
223
-
224
- // Check for duplicate candidateIndices
225
- const indices = (obj.candidates as Record<string, unknown>[])
226
- .map((c) => c.candidateIndex)
227
- .filter((i) => typeof i === 'number');
228
- const uniqueIndices = new Set(indices);
229
- if (indices.length !== uniqueIndices.size) {
230
- failures.push('Dreamer candidates have duplicate candidateIndex values');
231
- }
232
- }
233
-
234
- // Check generatedAt
235
- if (typeof obj.generatedAt !== 'string' || !isISO8601(obj.generatedAt)) {
236
- failures.push('Dreamer output missing valid ISO 8601 generatedAt');
237
- }
238
-
239
- return { valid: failures.length === 0, failures };
240
- }
241
-
242
- /**
243
- * Validate a Philosopher output contract.
244
- * Ensures the output is well-formed before passing to Scribe.
245
- */
246
-
247
- export function validatePhilosopherOutput(output: unknown): TrinityStageValidationResult {
248
- const failures: string[] = [];
249
-
250
- if (output === null || output === undefined || typeof output !== 'object') {
251
- return { valid: false, failures: ['Philosopher output must be a JSON object'] };
252
- }
253
-
254
- const obj = output as Record<string, unknown>;
255
-
256
- // Check valid flag
257
- if (obj.valid !== true) {
258
- if (typeof obj.reason === 'string' && obj.reason.length > 0) {
259
- failures.push(`Philosopher marked invalid: ${obj.reason}`);
260
- } else {
261
- failures.push('Philosopher output marked invalid with no reason');
262
- }
263
- }
264
-
265
- // Check judgments array
266
- if (!Array.isArray(obj.judgments)) {
267
- failures.push('Philosopher output must have a judgments array');
268
- } else {
269
- // Validate each judgment
270
-
271
- obj.judgments.forEach((judgment: unknown, idx: number) => {
272
- if (judgment === null || judgment === undefined || typeof judgment !== 'object') {
273
- failures.push(`Philosopher judgment at index ${idx} is not an object`);
274
- return;
275
- }
276
- const j = judgment as Record<string, unknown>;
277
-
278
- if (typeof j.candidateIndex !== 'number') {
279
- failures.push(`Philosopher judgment ${idx} missing candidateIndex`);
280
- }
281
- if (typeof j.critique !== 'string' || j.critique.trim().length === 0) {
282
- failures.push(`Philosopher judgment ${idx} missing non-empty critique`);
283
- }
284
- if (typeof j.principleAligned !== 'boolean') {
285
- failures.push(`Philosopher judgment ${idx} missing principleAligned boolean`);
286
- }
287
- if (typeof j.score !== 'number' || j.score < 0 || j.score > 1) {
288
- failures.push(`Philosopher judgment ${idx} has invalid score (must be 0-1)`);
289
- }
290
- if (typeof j.rank !== 'number' || j.rank < 1) {
291
- failures.push(`Philosopher judgment ${idx} has invalid rank (must be >= 1)`);
292
- }
293
- });
294
-
295
- // Check ranks are unique and sequential (1, 2, 3...)
296
- const ranks = (obj.judgments as Record<string, unknown>[])
297
- .map((j) => j.rank)
298
- .filter((r) => typeof r === 'number')
299
- .sort((a, b) => a - b);
300
- for (let i = 0; i < ranks.length; i++) {
301
- if (ranks[i] !== i + 1) {
302
- failures.push('Philosopher judgments must have sequential ranks starting from 1');
303
- break;
304
- }
305
- }
306
- }
307
-
308
- // Check overallAssessment
309
- if (typeof obj.overallAssessment !== 'string' || obj.overallAssessment.trim().length === 0) {
310
- failures.push('Philosopher output missing non-empty overallAssessment');
311
- }
312
-
313
- // Check generatedAt
314
- if (typeof obj.generatedAt !== 'string' || !isISO8601(obj.generatedAt)) {
315
- failures.push('Philosopher output missing valid ISO 8601 generatedAt');
316
- }
317
-
318
- return { valid: failures.length === 0, failures };
319
- }
320
-
321
- /**
322
- * Validate a TrinityDraftArtifact contract.
323
- * This is the final artifact before arbiter approval.
324
- */
325
- export function validateTrinityDraft(draft: unknown): TrinityStageValidationResult {
326
- const failures: string[] = [];
327
-
328
- if (draft === null || draft === undefined || typeof draft !== 'object') {
329
- return { valid: false, failures: ['Trinity draft must be a JSON object'] };
330
- }
331
-
332
- const obj = draft as Record<string, unknown>;
333
-
334
- // Required fields
335
- if (typeof obj.selectedCandidateIndex !== 'number') {
336
- failures.push('Trinity draft missing selectedCandidateIndex');
337
- }
338
- if (typeof obj.badDecision !== 'string' || obj.badDecision.trim().length === 0) {
339
- failures.push('Trinity draft missing non-empty badDecision');
340
- }
341
- if (typeof obj.betterDecision !== 'string' || obj.betterDecision.trim().length === 0) {
342
- failures.push('Trinity draft missing non-empty betterDecision');
343
- }
344
- if (typeof obj.rationale !== 'string' || obj.rationale.trim().length < 20) {
345
- failures.push('Trinity draft rationale must be at least 20 characters');
346
- }
347
- if (typeof obj.sessionId !== 'string' || obj.sessionId.trim().length === 0) {
348
- failures.push('Trinity draft missing non-empty sessionId');
349
- }
350
- if (typeof obj.principleId !== 'string' || obj.principleId.trim().length === 0) {
351
- failures.push('Trinity draft missing non-empty principleId');
352
- }
353
- if (typeof obj.sourceSnapshotRef !== 'string') {
354
- failures.push('Trinity draft missing sourceSnapshotRef');
355
- }
356
-
357
- // Semantic validation
358
- if (
359
- typeof obj.badDecision === 'string' &&
360
- typeof obj.betterDecision === 'string' &&
361
- obj.badDecision.trim() === obj.betterDecision.trim()
362
- ) {
363
- failures.push('Trinity draft badDecision and betterDecision are identical');
364
- }
365
-
366
- // Validate telemetry
367
- if (typeof obj.telemetry === 'object' && obj.telemetry !== null) {
368
- const t = obj.telemetry as Record<string, unknown>;
369
- if (t.chainMode !== 'trinity' && t.chainMode !== 'single-reflector') {
370
- failures.push('Trinity draft telemetry must have valid chainMode');
371
- }
372
- if (typeof t.dreamerPassed !== 'boolean') {
373
- failures.push('Trinity draft telemetry missing dreamerPassed boolean');
374
- }
375
- if (typeof t.philosopherPassed !== 'boolean') {
376
- failures.push('Trinity draft telemetry missing philosopherPassed boolean');
377
- }
378
- if (typeof t.scribePassed !== 'boolean') {
379
- failures.push('Trinity draft telemetry missing scribePassed boolean');
380
- }
381
- } else {
382
- failures.push('Trinity draft missing telemetry object');
383
- }
384
-
385
- return { valid: failures.length === 0, failures };
386
- }
387
-
388
- /**
389
- * Parse and validate a Dreamer output from JSON string.
390
- */
391
- export function parseAndValidateDreamerOutput(jsonString: string): TrinityStageValidationResult {
392
- try {
393
- const parsed = JSON.parse(jsonString);
394
- return validateDreamerOutput(parsed);
395
- } catch (err) {
396
- return {
397
- valid: false,
398
- failures: [`Failed to parse Dreamer output JSON: ${err instanceof Error ? err.message : String(err)}`],
399
- };
400
- }
401
- }
402
-
403
- /**
404
- * Parse and validate a Philosopher output from JSON string.
405
- */
406
- export function parseAndValidatePhilosopherOutput(jsonString: string): TrinityStageValidationResult {
407
- try {
408
- const parsed = JSON.parse(jsonString);
409
- return validatePhilosopherOutput(parsed);
410
- } catch (err) {
411
- return {
412
- valid: false,
413
- failures: [`Failed to parse Philosopher output JSON: ${err instanceof Error ? err.message : String(err)}`],
414
- };
415
- }
416
- }
417
-
418
- /**
419
- * Parse and validate a Trinity draft artifact from JSON string.
420
- */
421
- export function parseAndValidateTrinityDraft(jsonString: string): TrinityStageValidationResult {
422
- try {
423
- const parsed = JSON.parse(jsonString);
424
- return validateTrinityDraft(parsed);
425
- } catch (err) {
426
- return {
427
- valid: false,
428
- failures: [`Failed to parse Trinity draft JSON: ${err instanceof Error ? err.message : String(err)}`],
429
- };
430
- }
431
- }
432
-
433
- // ---------------------------------------------------------------------------
434
- // Core Arbiter
435
- // ---------------------------------------------------------------------------
436
-
437
- export interface ArbiterOptions {
438
- /**
439
- * Expected principle ID (from target selection).
440
- * If provided, the artifact's principleId must match this.
441
- */
442
- expectedPrincipleId?: string;
443
-
444
- /**
445
- * Expected session ID (from snapshot).
446
- * If provided, the artifact's sessionId must match this.
447
- */
448
- expectedSessionId?: string;
449
-
450
- /**
451
- * Minimum quality thresholds for reflection metrics.
452
- * If provided, artifacts failing these thresholds are rejected.
453
- */
454
- qualityThresholds?: {
455
- /**
456
- * Minimum thinkingModelDelta (delta in thinking model activation).
457
- * Must be > 0 for artifact to pass (cognitive improvement required).
458
- * Default: undefined (no threshold — only range check [-1, 1])
459
- */
460
- thinkingModelDeltaMin?: number;
461
- /**
462
- * Minimum planningRatioGain.
463
- * Must be >= -0.5 for artifact to pass (no catastrophic planning regression).
464
- * Default: undefined (no threshold — only range check [-1, 1])
465
- */
466
- planningRatioGainMin?: number;
467
- };
468
- }
469
-
470
- /**
471
- * Validate a raw reflection artifact against all arbiter rules.
472
- *
473
- * @param raw - The raw artifact JSON (already parsed)
474
- * @param options - Expected values for cross-validation
475
- * @returns ArbiterResult with passed/failed status and details
476
- */
477
- export function validateArtifact(
478
- raw: unknown,
479
- options: ArbiterOptions = {}
480
- ): ArbiterResult {
481
- const failures: ArbiterFailure[] = [];
482
-
483
- // Rule 1: Must be an object
484
- if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
485
- return {
486
- passed: false,
487
- rawInput: raw as RawReflectionArtifact,
488
- failures: [{ reason: 'Artifact must be a JSON object' }],
489
- };
490
- }
491
-
492
- const obj = raw as Record<string, unknown>;
493
-
494
- // Rule 2: Check for invalid flag (reflector said it couldn't generate)
495
- if (obj.invalid === true || obj.invalid === 'true') {
496
- return {
497
- passed: false,
498
- rawInput: obj as RawReflectionArtifact,
499
- failures: [{ reason: `Reflector marked artifact as invalid: ${String(obj.reason ?? 'no reason provided')}` }],
500
- };
501
- }
502
-
503
- // Rule 3: Required string fields must be present and non-empty
504
- const requiredFields: { key: keyof RawReflectionArtifact; label: string }[] = [
505
- { key: 'artifactId', label: 'artifactId' },
506
- { key: 'sessionId', label: 'sessionId' },
507
- { key: 'principleId', label: 'principleId' },
508
- { key: 'badDecision', label: 'badDecision' },
509
- { key: 'betterDecision', label: 'betterDecision' },
510
- { key: 'rationale', label: 'rationale' },
511
- { key: 'createdAt', label: 'createdAt' },
512
- ];
513
-
514
- for (const field of requiredFields) {
515
- const val = obj[field.key];
516
- if (!isNonEmptyString(val)) {
517
- failures.push({ reason: `Field '${field.label}' is missing or empty`, field: field.label });
518
- } else {
519
- // Additional checks for string fields
520
- if (field.label === 'artifactId' && !isValidUUID(String(val))) {
521
- failures.push({ reason: `Field '${field.label}' must be a valid UUID`, field: field.label });
522
- }
523
- if (field.label === 'createdAt' && !isISO8601(String(val))) {
524
- failures.push({ reason: `Field '${field.label}' must be a valid ISO 8601 timestamp`, field: field.label });
525
- }
526
- }
527
- }
528
-
529
- // Rule 4: Cross-validate principleId
530
- if (options.expectedPrincipleId !== undefined) {
531
- const {principleId} = obj;
532
- if (!isNonEmptyString(principleId)) {
533
- failures.push({ reason: 'principleId is required but missing', field: 'principleId' });
534
- } else if (String(principleId) !== options.expectedPrincipleId) {
535
- failures.push({
536
- reason: `principleId mismatch: expected '${options.expectedPrincipleId}', got '${String(principleId)}'`,
537
- field: 'principleId',
538
- });
539
- }
540
- }
541
-
542
- // Rule 5: Cross-validate sessionId
543
- if (options.expectedSessionId !== undefined) {
544
- const {sessionId} = obj;
545
- if (!isNonEmptyString(sessionId)) {
546
- failures.push({ reason: 'sessionId is required but missing', field: 'sessionId' });
547
- } else if (String(sessionId) !== options.expectedSessionId) {
548
- failures.push({
549
- reason: `sessionId mismatch: expected '${options.expectedSessionId}', got '${String(sessionId)}'`,
550
- field: 'sessionId',
551
- });
552
- }
553
- }
554
-
555
- // Rule 6: Check for placeholder values in text fields
556
- const textFields: (keyof RawReflectionArtifact)[] = [
557
- 'badDecision',
558
- 'betterDecision',
559
- 'rationale',
560
- ];
561
- for (const field of textFields) {
562
- const val = obj[field];
563
- if (isNonEmptyString(val)) {
564
- const strVal = String(val);
565
- if (containsPlaceholder(strVal)) {
566
- failures.push({ reason: `Field '${String(field)}' contains a placeholder value`, field: String(field) });
567
- }
568
- if (containsRawContent(strVal)) {
569
- failures.push({
570
- reason: `Field '${String(field)}' may contain raw/private content — not allowed in nocturnal artifacts`,
571
- field: String(field),
572
- });
573
- }
574
- }
575
- }
576
-
577
- // Rule 7: Check sourceSnapshotRef if present
578
- const {sourceSnapshotRef} = obj;
579
- if (sourceSnapshotRef !== undefined && !isNonEmptyString(sourceSnapshotRef)) {
580
- failures.push({ reason: 'Field "sourceSnapshotRef" must be a non-empty string if present', field: 'sourceSnapshotRef' });
581
- }
582
-
583
- // Rule 8: badDecision should not be identical to betterDecision
584
- const {badDecision} = obj;
585
- const {betterDecision} = obj;
586
- if (isNonEmptyString(badDecision) && isNonEmptyString(betterDecision)) {
587
- if (String(badDecision).trim() === String(betterDecision).trim()) {
588
- failures.push({
589
- reason: 'badDecision and betterDecision cannot be identical',
590
- field: 'betterDecision',
591
- });
592
- }
593
- }
594
-
595
- // Rule 9: Rationale should not be too short (needs explanation)
596
- const {rationale} = obj;
597
- if (isNonEmptyString(rationale) && String(rationale).trim().length < 20) {
598
- failures.push({
599
- reason: 'rationale is too short — must provide meaningful explanation',
600
- field: 'rationale',
601
- });
602
- }
603
-
604
- // Rule 10: Validate optional reflection quality metrics (if present)
605
- const {thinkingModelDelta} = obj;
606
- if (thinkingModelDelta !== undefined && typeof thinkingModelDelta !== 'number') {
607
- failures.push({
608
- reason: 'thinkingModelDelta must be a number if present',
609
- field: 'thinkingModelDelta',
610
- });
611
- } else if (typeof thinkingModelDelta === 'number' && (thinkingModelDelta < -1 || thinkingModelDelta > 1)) {
612
- failures.push({
613
- reason: 'thinkingModelDelta must be between -1 and 1',
614
- field: 'thinkingModelDelta',
615
- });
616
- }
617
-
618
- const {planningRatioGain} = obj;
619
- if (planningRatioGain !== undefined && typeof planningRatioGain !== 'number') {
620
- failures.push({
621
- reason: 'planningRatioGain must be a number if present',
622
- field: 'planningRatioGain',
623
- });
624
- } else if (typeof planningRatioGain === 'number' && (planningRatioGain < -1 || planningRatioGain > 1)) {
625
- failures.push({
626
- reason: 'planningRatioGain must be between -1 and 1',
627
- field: 'planningRatioGain',
628
- });
629
- }
630
-
631
- // Rule 11: Quality threshold gate — reject low-signal artifacts
632
- // A reflection artifact must show positive cognitive improvement (thinkingModelDelta > 0).
633
- // planningRatioGain must not show catastrophic regression (< -0.5).
634
- // #244: Use strict < so thinkingModelDelta=threshold passes (thin violations allowed at boundary)
635
- if (
636
- options.qualityThresholds?.thinkingModelDeltaMin !== undefined &&
637
- thinkingModelDelta !== undefined &&
638
- typeof thinkingModelDelta === 'number' &&
639
- thinkingModelDelta < options.qualityThresholds.thinkingModelDeltaMin
640
- ) {
641
- failures.push({
642
- reason: `thinkingModelDelta (${thinkingModelDelta}) does not meet minimum quality threshold (${options.qualityThresholds.thinkingModelDeltaMin}) — reflection shows no cognitive improvement`,
643
- field: 'thinkingModelDelta',
644
- });
645
- }
646
-
647
- if (
648
- options.qualityThresholds?.planningRatioGainMin !== undefined &&
649
- planningRatioGain !== undefined &&
650
- typeof planningRatioGain === 'number' &&
651
- planningRatioGain < options.qualityThresholds.planningRatioGainMin
652
- ) {
653
- failures.push({
654
- reason: `planningRatioGain (${planningRatioGain}) shows catastrophic planning regression — below minimum threshold (${options.qualityThresholds.planningRatioGainMin})`,
655
- field: 'planningRatioGain',
656
- });
657
- }
658
-
659
- // Final decision
660
- if (failures.length > 0) {
661
- return {
662
- passed: false,
663
- rawInput: obj as RawReflectionArtifact,
664
- failures,
665
- };
666
- }
667
-
668
- // Construct validated artifact
669
- const artifact: NocturnalArtifact = {
670
- artifactId: String(obj.artifactId),
671
- sessionId: String(obj.sessionId),
672
- principleId: String(obj.principleId),
673
- sourceSnapshotRef: isNonEmptyString(obj.sourceSnapshotRef) ? String(obj.sourceSnapshotRef) : '',
674
- badDecision: String(obj.badDecision),
675
- betterDecision: String(obj.betterDecision),
676
- rationale: String(obj.rationale),
677
- createdAt: String(obj.createdAt),
678
- thinkingModelDelta: typeof obj.thinkingModelDelta === 'number' ? obj.thinkingModelDelta : undefined,
679
- planningRatioGain: typeof obj.planningRatioGain === 'number' ? obj.planningRatioGain : undefined,
680
- };
681
-
682
- return {
683
- passed: true,
684
- artifact,
685
- failures: [],
686
- };
687
- }
688
-
689
- /**
690
- * Parse and validate a JSON string as a reflection artifact.
691
- *
692
- * @param jsonString - Raw JSON string from reflector
693
- * @param options - Expected values for cross-validation
694
- * @returns ArbiterResult
695
- */
696
- export function parseAndValidateArtifact(
697
- jsonString: string,
698
- options: ArbiterOptions = {}
699
- ): ArbiterResult {
700
- // Step 1: Parse JSON
701
-
702
-
703
- let parsed: unknown;
704
- try {
705
- parsed = JSON.parse(jsonString);
706
- } catch (err) {
707
- return {
708
- passed: false,
709
- failures: [{ reason: `Failed to parse JSON: ${err instanceof Error ? err.message : String(err)}` }],
710
- };
711
- }
712
-
713
- // Step 2: Validate
714
- return validateArtifact(parsed, options);
715
- }