principles-disciple 1.11.0 → 1.13.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 (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -31,20 +31,18 @@
31
31
  import * as fs from 'fs';
32
32
  import * as path from 'path';
33
33
  import { randomUUID } from 'crypto';
34
+ import type { RecentPainContext } from './evolution-worker.js';
34
35
  import {
35
- NocturnalTrajectoryExtractor,
36
36
  createNocturnalTrajectoryExtractor,
37
37
  computeThinkingModelDelta,
38
38
  type NocturnalSessionSnapshot,
39
39
  } from '../core/nocturnal-trajectory-extractor.js';
40
40
  import {
41
41
  NocturnalTargetSelector,
42
- selectNocturnalTarget,
43
42
  type NocturnalSelectionResult,
44
43
  type SkipReason,
45
44
  } from './nocturnal-target-selector.js';
46
45
  import {
47
- validateArtifact,
48
46
  parseAndValidateArtifact,
49
47
  validateTrinityDraft,
50
48
  type NocturnalArtifact,
@@ -69,7 +67,29 @@ import {
69
67
  type ThresholdSignals,
70
68
  } from '../core/adaptive-thresholds.js';
71
69
  import {
72
- checkWorkspaceIdle,
70
+ parseArtificerOutput,
71
+ resolveArtificerTargetRule,
72
+ shouldRunArtificer,
73
+ type ArtificerOutput,
74
+ type ArtificerTargetRuleResolution,
75
+ } from '../core/nocturnal-artificer.js';
76
+ import { validateRuleImplementationCandidate } from '../core/nocturnal-rule-implementation-validator.js';
77
+ import { refreshPrincipleLifecycle } from '../core/principle-internalization/lifecycle-refresh.js';
78
+ import {
79
+ createImplementationAssetDir,
80
+ deleteImplementationAssetDir,
81
+ getImplementationAssetRoot,
82
+ type CodeImplementationLineageMetadata,
83
+ } from '../core/code-implementation-storage.js';
84
+ import {
85
+ appendCandidateArtifactLineageRecord,
86
+ appendArtifactLineageRecord,
87
+ } from '../core/nocturnal-artifact-lineage.js';
88
+ import {
89
+ createImplementation,
90
+ deleteImplementation,
91
+ } from '../core/principle-tree-ledger.js';
92
+ import {
73
93
  checkPreflight,
74
94
  recordRunStart,
75
95
  recordRunEnd,
@@ -78,6 +98,7 @@ import {
78
98
  } from './nocturnal-runtime.js';
79
99
  import { NocturnalPathResolver } from '../core/nocturnal-paths.js';
80
100
  import { registerSample } from '../core/nocturnal-dataset.js';
101
+ import type { Implementation } from '../types/principle-tree-schema.js';
81
102
 
82
103
  // ---------------------------------------------------------------------------
83
104
  // Types
@@ -131,6 +152,27 @@ export interface NocturnalRunDiagnostics {
131
152
  persisted: boolean;
132
153
  /** Persistence path (if persisted) */
133
154
  persistedPath?: string;
155
+ /** Code-candidate sidecar diagnostics */
156
+ artificer: NocturnalArtificerDiagnostics;
157
+ }
158
+
159
+ export interface NocturnalArtificerDiagnostics {
160
+ status: 'skipped' | 'validation_failed' | 'persisted_candidate';
161
+ reason?:
162
+ | 'behavioral_artifact_unavailable'
163
+ | 'no_deterministic_rule'
164
+ | 'insufficient_signal_density'
165
+ | 'missing_scribe_input'
166
+ | 'parse_failed'
167
+ | 'rule_mismatch'
168
+ | 'validator_rejected'
169
+ | 'persistence_failed';
170
+ ruleResolution: ArtificerTargetRuleResolution | null;
171
+ validationFailures: string[];
172
+ implementationId?: string;
173
+ artifactId?: string;
174
+ ruleId?: string;
175
+ persistedPath?: string;
134
176
  }
135
177
 
136
178
  /**
@@ -179,7 +221,27 @@ export interface NocturnalServiceOptions {
179
221
  * When provided, the target selector uses it for ranking bias and diagnostics enrichment.
180
222
  * This threads recent pain signals into sleep_reflection targeting without merging task kinds.
181
223
  */
182
- painContext?: import('../service/evolution-worker.js').RecentPainContext;
224
+ painContext?: RecentPainContext;
225
+
226
+ /**
227
+ * Override the principleId (skip Selector stage).
228
+ * When provided with snapshotOverride, the Selector stage is skipped and the provided
229
+ * principleId and snapshot are used directly for Trinity execution.
230
+ * This unifies NocturnalWorkflowManager with executeNocturnalReflectionAsync.
231
+ */
232
+ principleIdOverride?: string;
233
+
234
+ /**
235
+ * Override the snapshot (skip Selector stage).
236
+ * Must be provided together with principleIdOverride to skip Selector.
237
+ */
238
+ snapshotOverride?: NocturnalSessionSnapshot;
239
+
240
+ /**
241
+ * Override the Artificer JSON output (for testing).
242
+ * When omitted, a deterministic local candidate is synthesized.
243
+ */
244
+ artificerOutputOverride?: string;
183
245
  }
184
246
 
185
247
  // ---------------------------------------------------------------------------
@@ -209,8 +271,11 @@ function invokeStubReflector(
209
271
  const hasGateBlocks = snapshot.stats.totalGateBlocks > 0;
210
272
 
211
273
  // Detect what kind of signal is available and craft appropriate artifact
274
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
212
275
  let badDecision: string;
276
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
213
277
  let betterDecision: string;
278
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
214
279
  let rationale: string;
215
280
 
216
281
  if (hasGateBlocks) {
@@ -283,6 +348,272 @@ function persistArtifact(
283
348
  return artifactPath;
284
349
  }
285
350
 
351
+ function buildPainRefs(snapshot: NocturnalSessionSnapshot): string[] {
352
+ return snapshot.painEvents.map(
353
+ (painEvent) =>
354
+ `pain:${painEvent.source}:${painEvent.createdAt}:${(painEvent.reason ?? '').trim()}`
355
+ );
356
+ }
357
+
358
+ function buildGateBlockRefs(snapshot: NocturnalSessionSnapshot): string[] {
359
+ return snapshot.gateBlocks.map(
360
+ (gateBlock) =>
361
+ `gate:${gateBlock.toolName}:${gateBlock.createdAt}:${gateBlock.reason.trim()}`
362
+ );
363
+ }
364
+
365
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe artifact construction */
366
+ function buildDefaultArtificerOutput(
367
+ ruleId: string,
368
+ artifact: NocturnalArtifact,
369
+ sourceSnapshotRef: string,
370
+ sourcePainIds: string[],
371
+ sourceGateBlockIds: string[]
372
+ ): ArtificerOutput {
373
+ return {
374
+ ruleId,
375
+ implementationType: 'code',
376
+ candidateSource: [
377
+ 'export const meta = {',
378
+ ` name: ${JSON.stringify(`nocturnal-${ruleId.toLowerCase()}`)},`,
379
+ ' version: "1.0.0",',
380
+ ` ruleId: ${JSON.stringify(ruleId)},`,
381
+ ` coversCondition: ${JSON.stringify(artifact.betterDecision)},`,
382
+ '};',
383
+ '',
384
+ 'export function evaluate(input, helpers) {',
385
+ ' const riskPath = helpers.isRiskPath();',
386
+ ' const toolName = helpers.getToolName();',
387
+ ' const planStatus = helpers.getPlanStatus();',
388
+ " if (riskPath && toolName === 'write' && planStatus !== 'READY') {",
389
+ ' return {',
390
+ " decision: 'requireApproval',",
391
+ ' matched: true,',
392
+ ` reason: ${JSON.stringify(artifact.rationale)},`,
393
+ ' };',
394
+ ' }',
395
+ ' return {',
396
+ " decision: 'allow',",
397
+ ' matched: false,',
398
+ " reason: 'not-applicable',",
399
+ ' };',
400
+ '}',
401
+ ].join('\n'),
402
+ helperUsage: ['isRiskPath', 'getToolName', 'getPlanStatus'],
403
+ expectedDecision: 'requireApproval',
404
+ rationale: artifact.rationale,
405
+ lineage: {
406
+ artifactKind: 'rule-implementation-candidate',
407
+ sourceSnapshotRef,
408
+ sourcePainIds,
409
+ sourceGateBlockIds,
410
+ },
411
+ };
412
+ }
413
+
414
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe candidate persistence */
415
+ function persistCodeCandidate(
416
+ workspaceDir: string,
417
+ stateDir: string,
418
+ artifact: NocturnalArtifact,
419
+ selectedPrincipleId: string,
420
+ selectedSessionId: string,
421
+ parsedArtificer: ArtificerOutput
422
+ ): NocturnalArtificerDiagnostics {
423
+ const implementationId = `IMPL-${randomUUID()}`;
424
+ const artifactId = `artifact-${randomUUID()}`;
425
+ const now = new Date().toISOString();
426
+ const assetRoot = getImplementationAssetRoot(stateDir, implementationId);
427
+ const entryPath = path.join(assetRoot, 'entry.js');
428
+ const lineage: CodeImplementationLineageMetadata = {
429
+ principleId: selectedPrincipleId,
430
+ ruleId: parsedArtificer.ruleId,
431
+ sourceSnapshotRef: artifact.sourceSnapshotRef,
432
+ sourcePainIds: [...parsedArtificer.lineage.sourcePainIds],
433
+ sourceGateBlockIds: [...parsedArtificer.lineage.sourceGateBlockIds],
434
+ sourceSessionId: selectedSessionId,
435
+ artificerArtifactId: artifactId,
436
+ };
437
+
438
+ const implementation: Implementation = {
439
+ id: implementationId,
440
+ ruleId: parsedArtificer.ruleId,
441
+ type: 'code',
442
+ path: entryPath,
443
+ version: now,
444
+ coversCondition: parsedArtificer.rationale,
445
+ coveragePercentage: 0,
446
+ lifecycleState: 'candidate',
447
+ createdAt: now,
448
+ updatedAt: now,
449
+ };
450
+
451
+ try {
452
+ createImplementation(stateDir, implementation);
453
+ createImplementationAssetDir(stateDir, implementationId, now, {
454
+ entrySource: parsedArtificer.candidateSource,
455
+ lineage,
456
+ });
457
+ appendCandidateArtifactLineageRecord(workspaceDir, {
458
+ artifactId,
459
+ principleId: selectedPrincipleId,
460
+ ruleId: parsedArtificer.ruleId,
461
+ sessionId: selectedSessionId,
462
+ sourceSnapshotRef: artifact.sourceSnapshotRef,
463
+ sourcePainIds: lineage.sourcePainIds,
464
+ sourceGateBlockIds: lineage.sourceGateBlockIds,
465
+ storagePath: assetRoot,
466
+ implementationId,
467
+ createdAt: now,
468
+ });
469
+ refreshPrincipleLifecycle(workspaceDir, stateDir);
470
+ return {
471
+ status: 'persisted_candidate',
472
+ ruleResolution: {
473
+ status: 'selected',
474
+ ruleId: parsedArtificer.ruleId,
475
+ reason: 'evidence-winner',
476
+ scores: [],
477
+ },
478
+ validationFailures: [],
479
+ implementationId,
480
+ artifactId,
481
+ ruleId: parsedArtificer.ruleId,
482
+ persistedPath: assetRoot,
483
+ };
484
+ } catch (error: unknown) {
485
+ deleteImplementationAssetDir(stateDir, implementationId);
486
+ try {
487
+ deleteImplementation(stateDir, implementationId);
488
+ } catch {
489
+ // Best effort cleanup to avoid leaving a half-created candidate discoverable.
490
+ }
491
+ return {
492
+ status: 'validation_failed',
493
+ reason: 'persistence_failed',
494
+ ruleResolution: {
495
+ status: 'selected',
496
+ ruleId: parsedArtificer.ruleId,
497
+ reason: 'evidence-winner',
498
+ scores: [],
499
+ },
500
+ validationFailures: [String(error)],
501
+ ruleId: parsedArtificer.ruleId,
502
+ };
503
+ }
504
+ }
505
+
506
+ /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe candidate persistence */
507
+ function maybePersistArtificerCandidate(
508
+ workspaceDir: string,
509
+ stateDir: string,
510
+ selectedPrincipleId: string,
511
+ selectedSessionId: string,
512
+ snapshot: NocturnalSessionSnapshot,
513
+ artifact: NocturnalArtifact,
514
+ options: NocturnalServiceOptions
515
+ ): NocturnalArtificerDiagnostics {
516
+ const ruleResolution = resolveArtificerTargetRule(
517
+ stateDir,
518
+ selectedPrincipleId,
519
+ snapshot
520
+ );
521
+
522
+ if (ruleResolution.status !== 'selected') {
523
+ return {
524
+ status: 'skipped',
525
+ reason: 'no_deterministic_rule',
526
+ ruleResolution,
527
+ validationFailures: [],
528
+ };
529
+ }
530
+
531
+ // #219: Detect fallback data source and warn about potential signal inaccuracy
532
+ const validationFailures: string[] = [];
533
+ if (snapshot._dataSource === 'pain_context_fallback') {
534
+ validationFailures.push('fallback_snapshot: stats derived from pain context only (trajectory extractor failed) - signal counts may be undercounted');
535
+ }
536
+
537
+ if (!shouldRunArtificer(snapshot, ruleResolution)) {
538
+ return {
539
+ status: 'skipped',
540
+ reason: 'insufficient_signal_density',
541
+ ruleResolution,
542
+ validationFailures,
543
+ ruleId: ruleResolution.ruleId,
544
+ };
545
+ }
546
+
547
+ if (!artifact.betterDecision || !artifact.rationale) {
548
+ return {
549
+ status: 'skipped',
550
+ reason: 'missing_scribe_input',
551
+ ruleResolution,
552
+ validationFailures: [],
553
+ ruleId: ruleResolution.ruleId,
554
+ };
555
+ }
556
+
557
+ const sourcePainIds = buildPainRefs(snapshot);
558
+ const sourceGateBlockIds = buildGateBlockRefs(snapshot);
559
+ const parsedArtificer =
560
+ options.artificerOutputOverride !== undefined
561
+ ? parseArtificerOutput(options.artificerOutputOverride)
562
+ : buildDefaultArtificerOutput(
563
+ ruleResolution.ruleId,
564
+ artifact,
565
+ artifact.sourceSnapshotRef,
566
+ sourcePainIds,
567
+ sourceGateBlockIds
568
+ );
569
+
570
+ if (!parsedArtificer) {
571
+ return {
572
+ status: 'validation_failed',
573
+ reason: 'parse_failed',
574
+ ruleResolution,
575
+ validationFailures: ['Artificer output could not be parsed.'],
576
+ ruleId: ruleResolution.ruleId,
577
+ };
578
+ }
579
+
580
+ if (parsedArtificer.ruleId !== ruleResolution.ruleId) {
581
+ return {
582
+ status: 'validation_failed',
583
+ reason: 'rule_mismatch',
584
+ ruleResolution,
585
+ validationFailures: [
586
+ `Resolved rule ${ruleResolution.ruleId} did not match candidate rule ${parsedArtificer.ruleId}.`,
587
+ ],
588
+ ruleId: ruleResolution.ruleId,
589
+ };
590
+ }
591
+
592
+ const validation = validateRuleImplementationCandidate(parsedArtificer.candidateSource);
593
+ if (!validation.passed) {
594
+ return {
595
+ status: 'validation_failed',
596
+ reason: 'validator_rejected',
597
+ ruleResolution,
598
+ validationFailures: validation.failures.map((failure) => failure.message),
599
+ ruleId: ruleResolution.ruleId,
600
+ };
601
+ }
602
+
603
+ const persisted = persistCodeCandidate(
604
+ workspaceDir,
605
+ stateDir,
606
+ artifact,
607
+ selectedPrincipleId,
608
+ selectedSessionId,
609
+ parsedArtificer
610
+ );
611
+ return {
612
+ ...persisted,
613
+ ruleResolution,
614
+ };
615
+ }
616
+
286
617
  // ---------------------------------------------------------------------------
287
618
  // Main Orchestrator
288
619
  // ---------------------------------------------------------------------------
@@ -320,6 +651,11 @@ export function executeNocturnalReflection(
320
651
  arbiterResult: null,
321
652
  executabilityResult: null,
322
653
  persisted: false,
654
+ artificer: {
655
+ status: 'skipped',
656
+ ruleResolution: null,
657
+ validationFailures: [],
658
+ },
323
659
  };
324
660
 
325
661
  // -------------------------------------------------------------------------
@@ -407,10 +743,11 @@ export function executeNocturnalReflection(
407
743
  // -------------------------------------------------------------------------
408
744
  // Step 5: Artifact generation (Trinity or single-reflector)
409
745
  // -------------------------------------------------------------------------
746
+ // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in all branches
410
747
  let trinityArtifact: TrinityDraftArtifact | null = null;
411
748
  let trinityResult: TrinityResult | null = null;
749
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all branches before use at line 884
412
750
  let rawJson: string;
413
- let chainModeUsed: 'trinity' | 'single-reflector' = 'single-reflector';
414
751
 
415
752
  if (options.skipReflector) {
416
753
  // Caller provided explicit artifact — used for testing arbiter/executability
@@ -430,7 +767,6 @@ export function executeNocturnalReflection(
430
767
  diagnostics.trinityAttempted = true;
431
768
  diagnostics.trinityResult = trinityResult;
432
769
  diagnostics.chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
433
- chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
434
770
 
435
771
  if (!trinityResult.success) {
436
772
  // Trinity failed — fail closed (same semantics as production)
@@ -457,7 +793,7 @@ export function executeNocturnalReflection(
457
793
  // Validate Trinity draft
458
794
  const draftValidation = validateTrinityDraft(trinityResult.artifact);
459
795
  if (!draftValidation.valid) {
460
- const failures = draftValidation.failures;
796
+ const {failures} = draftValidation;
461
797
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
462
798
  console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
463
799
  });
@@ -477,7 +813,7 @@ export function executeNocturnalReflection(
477
813
  diagnostics,
478
814
  };
479
815
  }
480
- trinityArtifact = trinityResult.artifact!;
816
+ trinityArtifact = trinityResult.artifact!; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: artifact is validated by validateTrinityDraft which returns valid: true when artifact exists
481
817
  // Convert Trinity draft to arbiter-compatible artifact
482
818
  const artifactData = draftToArtifact(trinityArtifact);
483
819
  rawJson = JSON.stringify(artifactData);
@@ -502,14 +838,13 @@ export function executeNocturnalReflection(
502
838
  trinityResult = runTrinity({ snapshot, principleId: selectedPrincipleId, config: effectiveConfig });
503
839
  diagnostics.trinityResult = trinityResult;
504
840
  diagnostics.chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
505
- chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
506
841
 
507
842
  if (trinityResult.success) {
508
843
  // Validate Trinity draft
509
844
  const draftValidation = validateTrinityDraft(trinityResult.artifact);
510
845
  if (!draftValidation.valid) {
511
846
  // Trinity draft invalid — fail closed
512
- const failures = draftValidation.failures;
847
+ const {failures} = draftValidation;
513
848
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
514
849
  console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
515
850
  });
@@ -529,7 +864,7 @@ export function executeNocturnalReflection(
529
864
  diagnostics,
530
865
  };
531
866
  }
532
- trinityArtifact = trinityResult.artifact!;
867
+ trinityArtifact = trinityResult.artifact!; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: artifact is validated by validateTrinityDraft which returns valid: true when artifact exists
533
868
  // Convert Trinity draft to arbiter-compatible artifact
534
869
  const artifactData = draftToArtifact(trinityArtifact);
535
870
  rawJson = JSON.stringify(artifactData);
@@ -631,6 +966,7 @@ export function executeNocturnalReflection(
631
966
  boundedAction: execResult.boundedAction,
632
967
  };
633
968
 
969
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, catch has early return
634
970
  let persistedPath: string;
635
971
  try {
636
972
  persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
@@ -663,6 +999,34 @@ export function executeNocturnalReflection(
663
999
  console.warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
664
1000
  }
665
1001
 
1002
+ try {
1003
+ appendArtifactLineageRecord(workspaceDir, {
1004
+ artifactKind: 'behavioral-sample',
1005
+ artifactId: arbiterResult.artifact.artifactId,
1006
+ principleId: selectedPrincipleId,
1007
+ ruleId: null,
1008
+ sessionId: selectedSessionId,
1009
+ sourceSnapshotRef: arbiterResult.artifact.sourceSnapshotRef,
1010
+ sourcePainIds: buildPainRefs(snapshot),
1011
+ sourceGateBlockIds: buildGateBlockRefs(snapshot),
1012
+ storagePath: persistedPath,
1013
+ implementationId: null,
1014
+ createdAt: arbiterResult.artifact.createdAt,
1015
+ });
1016
+ } catch (err) {
1017
+ console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1018
+ }
1019
+
1020
+ diagnostics.artificer = maybePersistArtificerCandidate(
1021
+ workspaceDir,
1022
+ stateDir,
1023
+ selectedPrincipleId,
1024
+ selectedSessionId,
1025
+ snapshot,
1026
+ arbiterResult.artifact,
1027
+ options
1028
+ );
1029
+
666
1030
  // -------------------------------------------------------------------------
667
1031
  // Step 9: Record run success
668
1032
  // -------------------------------------------------------------------------
@@ -726,6 +1090,7 @@ export async function executeNocturnalReflectionAsync(
726
1090
 
727
1091
  // If runtime adapter is provided, use async Trinity path
728
1092
  if (options.runtimeAdapter) {
1093
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
729
1094
  return executeNocturnalReflectionWithAdapter(workspaceDir, stateDir, options);
730
1095
  }
731
1096
 
@@ -752,6 +1117,11 @@ async function executeNocturnalReflectionWithAdapter(
752
1117
  arbiterResult: null,
753
1118
  executabilityResult: null,
754
1119
  persisted: false,
1120
+ artificer: {
1121
+ status: 'skipped',
1122
+ ruleResolution: null,
1123
+ validationFailures: [],
1124
+ },
755
1125
  };
756
1126
 
757
1127
  // Step 1: Pre-flight check
@@ -768,51 +1138,97 @@ async function executeNocturnalReflectionWithAdapter(
768
1138
  return { success: false, noTargetSelected: false, validationFailed: false, validationFailures: [], diagnostics };
769
1139
  }
770
1140
 
771
- // Step 2: Target selection
772
- const extractor = createNocturnalTrajectoryExtractor(workspaceDir, stateDir);
773
- const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
774
- idleCheckOverride: options.idleCheckOverride,
775
- recentPainContext: options.painContext,
776
- });
1141
+ // Step 2: Target selection (or use override to skip)
1142
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned immediately in all branches before use
1143
+ let selectedPrincipleId: string | undefined;
1144
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- Reason: assigned immediately in all branches before use
1145
+ let selectedSessionId: string | undefined;
1146
+ // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in all branches
1147
+ let snapshot: NocturnalSessionSnapshot | null = null;
1148
+
1149
+ if (options.principleIdOverride && options.snapshotOverride) {
1150
+ // Skip Selector: use provided principleId and snapshot directly
1151
+ selectedPrincipleId = options.principleIdOverride;
1152
+ selectedSessionId = options.snapshotOverride.sessionId;
1153
+ snapshot = options.snapshotOverride;
1154
+ console.log(`[nocturnal-service] Using override: principleId=${selectedPrincipleId}, sessionId=${selectedSessionId}`);
1155
+ // Calculate violation density from snapshot stats for meaningful diagnostics
1156
+ const snapStats = options.snapshotOverride.stats;
1157
+ const totalToolCalls = snapStats?.totalToolCalls ?? 0;
1158
+ const failureCount = snapStats?.failureCount ?? 0;
1159
+ const violationDensity = totalToolCalls > 0 ? failureCount / totalToolCalls : 0;
1160
+ diagnostics.selection = {
1161
+ decision: 'selected',
1162
+ selectedPrincipleId,
1163
+ selectedSessionId,
1164
+ skipReason: undefined,
1165
+ diagnostics: {
1166
+ totalEvaluablePrinciples: 1, // We provided one principle via override
1167
+ filteredByCooldown: 0,
1168
+ passedPrinciples: [selectedPrincipleId],
1169
+ violatingSessionCount: 1, // The session we're using
1170
+ selectedSessionViolationDensity: violationDensity,
1171
+ selectedPrincipleScore: 100, // Override means high priority
1172
+ scoringBreakdown: { override: 100 },
1173
+ idleCheckPassed: true,
1174
+ cooldownCheckPassed: true,
1175
+ quotaCheckPassed: true,
1176
+ },
1177
+ };
1178
+ diagnostics.idle = { isIdle: true, mostRecentActivityAt: 0, idleForMs: 0, userActiveSessions: 0, abandonedSessionIds: [], trajectoryGuardrailConfirmsIdle: true, reason: 'selector skipped (override provided)' };
1179
+ } else {
1180
+ // Normal Selector path
1181
+ console.log(`[nocturnal-service] Step 2/7: Target selection (normal path)`);
1182
+ const extractor = createNocturnalTrajectoryExtractor(workspaceDir, stateDir);
1183
+ const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
1184
+ idleCheckOverride: options.idleCheckOverride,
1185
+ recentPainContext: options.painContext,
1186
+ });
777
1187
 
778
- const selection = selector.select();
779
- diagnostics.selection = selection;
1188
+ const selection = selector.select();
1189
+ diagnostics.selection = selection;
1190
+ console.log(`[nocturnal-service] Selector result: decision=${selection.decision}, skipReason=${selection.skipReason ?? 'none'}`);
780
1191
 
781
- if (selection.decision === 'skip') {
782
- return {
783
- success: false,
784
- noTargetSelected: true,
785
- skipReason: selection.skipReason,
786
- validationFailed: false,
787
- validationFailures: [],
788
- diagnostics,
789
- };
790
- }
1192
+ if (selection.decision === 'skip') {
1193
+ console.warn(`[nocturnal-service] Target selection skipped: ${selection.skipReason}`);
1194
+ return {
1195
+ success: false,
1196
+ noTargetSelected: true,
1197
+ skipReason: selection.skipReason,
1198
+ validationFailed: false,
1199
+ validationFailures: [],
1200
+ diagnostics,
1201
+ };
1202
+ }
791
1203
 
792
- const { selectedPrincipleId, selectedSessionId } = selection;
1204
+ // eslint-disable-next-line @typescript-eslint/prefer-destructuring -- Reason: selectedPrincipleId/selectedSessionId are reassignable outer lets - destructuring would shadow
1205
+ selectedPrincipleId = selection.selectedPrincipleId;
1206
+ // eslint-disable-next-line @typescript-eslint/prefer-destructuring -- Reason: selectedPrincipleId/selectedSessionId are reassignable outer lets - destructuring would shadow
1207
+ selectedSessionId = selection.selectedSessionId;
793
1208
 
794
- if (!selectedPrincipleId || !selectedSessionId) {
795
- return {
796
- success: false,
797
- noTargetSelected: true,
798
- validationFailed: false,
799
- validationFailures: [],
800
- diagnostics,
801
- };
802
- }
1209
+ if (!selectedPrincipleId || !selectedSessionId) {
1210
+ return {
1211
+ success: false,
1212
+ noTargetSelected: true,
1213
+ validationFailed: false,
1214
+ validationFailures: [],
1215
+ diagnostics,
1216
+ };
1217
+ }
803
1218
 
804
- const snapshot = extractor.getNocturnalSessionSnapshot(selectedSessionId);
805
- if (!snapshot) {
806
- return {
807
- success: false,
808
- noTargetSelected: true,
809
- skipReason: 'insufficient_snapshot_data',
810
- validationFailed: false,
811
- validationFailures: [],
812
- diagnostics,
813
- };
1219
+ snapshot = extractor.getNocturnalSessionSnapshot(selectedSessionId);
1220
+ if (!snapshot) {
1221
+ return {
1222
+ success: false,
1223
+ noTargetSelected: true,
1224
+ skipReason: 'insufficient_snapshot_data',
1225
+ validationFailed: false,
1226
+ validationFailures: [],
1227
+ diagnostics,
1228
+ };
1229
+ }
1230
+ diagnostics.idle = { isIdle: true, mostRecentActivityAt: 0, idleForMs: 0, userActiveSessions: 0, abandonedSessionIds: [], trajectoryGuardrailConfirmsIdle: true, reason: 'preflight passed' };
814
1231
  }
815
- diagnostics.idle = { isIdle: true, mostRecentActivityAt: 0, idleForMs: 0, userActiveSessions: 0, abandonedSessionIds: [], trajectoryGuardrailConfirmsIdle: true, reason: 'preflight passed' };
816
1232
 
817
1233
  // Step 3: Record run start
818
1234
  void recordRunStart(stateDir, selectedPrincipleId).catch((err) => {
@@ -820,10 +1236,11 @@ async function executeNocturnalReflectionWithAdapter(
820
1236
  });
821
1237
 
822
1238
  // Step 4: Trinity execution via adapter (async)
1239
+ // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in all branches
823
1240
  let trinityArtifact: TrinityDraftArtifact | null = null;
824
1241
  let trinityResult: TrinityResult | null = null;
1242
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all branches before use
825
1243
  let rawJson: string;
826
- let chainModeUsed: 'trinity' | 'single-reflector' = 'single-reflector';
827
1244
 
828
1245
  if (options.skipReflector) {
829
1246
  if (!options.reflectorOutputOverride) {
@@ -841,7 +1258,6 @@ async function executeNocturnalReflectionWithAdapter(
841
1258
  diagnostics.trinityAttempted = true;
842
1259
  diagnostics.trinityResult = trinityResult;
843
1260
  diagnostics.chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
844
- chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
845
1261
 
846
1262
  if (!trinityResult.success) {
847
1263
  const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
@@ -851,7 +1267,7 @@ async function executeNocturnalReflectionWithAdapter(
851
1267
  adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
852
1268
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Trinity override failed: ${failures.join('; ')}`], snapshot, diagnostics };
853
1269
  }
854
- trinityArtifact = trinityResult.artifact!;
1270
+ trinityArtifact = trinityResult.artifact!; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: artifact is validated by validateTrinityDraft which returns valid: true when artifact exists
855
1271
  const artifactData = draftToArtifact(trinityArtifact);
856
1272
  rawJson = JSON.stringify(artifactData);
857
1273
  } else {
@@ -867,19 +1283,18 @@ async function executeNocturnalReflectionWithAdapter(
867
1283
  trinityResult = await runTrinityAsync({ snapshot, principleId: selectedPrincipleId, config: trinityConfig });
868
1284
  diagnostics.trinityResult = trinityResult;
869
1285
  diagnostics.chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
870
- chainModeUsed = trinityResult.success ? 'trinity' : 'single-reflector';
871
1286
 
872
1287
  if (trinityResult.success) {
873
1288
  const draftValidation = validateTrinityDraft(trinityResult.artifact);
874
1289
  if (!draftValidation.valid) {
875
- const failures = draftValidation.failures;
1290
+ const {failures} = draftValidation;
876
1291
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
877
1292
  console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
878
1293
  });
879
1294
  adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
880
1295
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, snapshot, diagnostics };
881
1296
  }
882
- trinityArtifact = trinityResult.artifact!;
1297
+ trinityArtifact = trinityResult.artifact!; // eslint-disable-line @typescript-eslint/no-non-null-assertion -- Reason: artifact is validated by validateTrinityDraft which returns valid: true when artifact exists
883
1298
  const artifactData = draftToArtifact(trinityArtifact);
884
1299
  rawJson = JSON.stringify(artifactData);
885
1300
  } else {
@@ -929,6 +1344,7 @@ async function executeNocturnalReflectionWithAdapter(
929
1344
 
930
1345
  // Step 7: Persist artifact
931
1346
  const artifactWithBoundedAction = { ...arbiterResult.artifact, boundedAction: execResult.boundedAction };
1347
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, catch has early return
932
1348
  let persistedPath: string;
933
1349
  try {
934
1350
  persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
@@ -948,6 +1364,34 @@ async function executeNocturnalReflectionWithAdapter(
948
1364
  console.warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
949
1365
  }
950
1366
 
1367
+ try {
1368
+ appendArtifactLineageRecord(workspaceDir, {
1369
+ artifactKind: 'behavioral-sample',
1370
+ artifactId: arbiterResult.artifact.artifactId,
1371
+ principleId: selectedPrincipleId,
1372
+ ruleId: null,
1373
+ sessionId: selectedSessionId,
1374
+ sourceSnapshotRef: arbiterResult.artifact.sourceSnapshotRef,
1375
+ sourcePainIds: buildPainRefs(snapshot),
1376
+ sourceGateBlockIds: buildGateBlockRefs(snapshot),
1377
+ storagePath: persistedPath,
1378
+ implementationId: null,
1379
+ createdAt: arbiterResult.artifact.createdAt,
1380
+ });
1381
+ } catch (err) {
1382
+ console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1383
+ }
1384
+
1385
+ diagnostics.artificer = maybePersistArtificerCandidate(
1386
+ workspaceDir,
1387
+ stateDir,
1388
+ selectedPrincipleId,
1389
+ selectedSessionId,
1390
+ snapshot,
1391
+ arbiterResult.artifact,
1392
+ options
1393
+ );
1394
+
951
1395
  // Step 9: Record run success
952
1396
  void recordRunEnd(stateDir, 'success', { sampleCount: 1 }).catch((err) => {
953
1397
  console.warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
@@ -982,9 +1426,9 @@ async function executeNocturnalReflectionWithAdapter(
982
1426
  */
983
1427
  export function listApprovedNocturnalArtifacts(
984
1428
  workspaceDir: string
985
- ): Array<NocturnalArtifact & { persistedAt: string; boundedAction?: BoundedAction }> {
1429
+ ): (NocturnalArtifact & { persistedAt: string; boundedAction?: BoundedAction })[] {
986
1430
  const samplePaths = NocturnalPathResolver.listApprovedSamples(workspaceDir);
987
- const artifacts: Array<NocturnalArtifact & { persistedAt: string; boundedAction?: BoundedAction }> = [];
1431
+ const artifacts: (NocturnalArtifact & { persistedAt: string; boundedAction?: BoundedAction })[] = [];
988
1432
 
989
1433
  for (const samplePath of samplePaths) {
990
1434
  try {