@principles/pd-cli 1.133.0 → 1.135.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 (215) hide show
  1. package/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
  2. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
  3. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
  4. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
  5. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
  6. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
  7. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
  8. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
  9. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
  10. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
  11. package/dist/commands/admission-gate.d.ts +24 -0
  12. package/dist/commands/admission-gate.d.ts.map +1 -0
  13. package/dist/commands/admission-gate.js +29 -0
  14. package/dist/commands/admission-gate.js.map +1 -0
  15. package/dist/commands/candidate.d.ts.map +1 -1
  16. package/dist/commands/candidate.js +72 -6
  17. package/dist/commands/candidate.js.map +1 -1
  18. package/dist/commands/console.d.ts.map +1 -1
  19. package/dist/commands/console.js +42 -0
  20. package/dist/commands/console.js.map +1 -1
  21. package/dist/commands/demo-story-a.d.ts.map +1 -1
  22. package/dist/commands/demo-story-a.js +4 -2
  23. package/dist/commands/demo-story-a.js.map +1 -1
  24. package/dist/commands/diagnose.d.ts.map +1 -1
  25. package/dist/commands/diagnose.js +155 -8
  26. package/dist/commands/diagnose.js.map +1 -1
  27. package/dist/commands/errors-list.d.ts +25 -0
  28. package/dist/commands/errors-list.d.ts.map +1 -0
  29. package/dist/commands/errors-list.js +368 -0
  30. package/dist/commands/errors-list.js.map +1 -0
  31. package/dist/commands/health-codex.d.ts +7 -0
  32. package/dist/commands/health-codex.d.ts.map +1 -0
  33. package/dist/commands/health-codex.js +209 -0
  34. package/dist/commands/health-codex.js.map +1 -0
  35. package/dist/commands/health.d.ts +24 -0
  36. package/dist/commands/health.d.ts.map +1 -1
  37. package/dist/commands/health.js +48 -0
  38. package/dist/commands/health.js.map +1 -1
  39. package/dist/commands/intent.d.ts +21 -4
  40. package/dist/commands/intent.d.ts.map +1 -1
  41. package/dist/commands/intent.js +88 -27
  42. package/dist/commands/intent.js.map +1 -1
  43. package/dist/commands/pain-evidence.d.ts +6 -0
  44. package/dist/commands/pain-evidence.d.ts.map +1 -1
  45. package/dist/commands/pain-evidence.js +6 -0
  46. package/dist/commands/pain-evidence.js.map +1 -1
  47. package/dist/commands/pain-record.d.ts.map +1 -1
  48. package/dist/commands/pain-record.js +21 -0
  49. package/dist/commands/pain-record.js.map +1 -1
  50. package/dist/commands/pain-retry.d.ts +4 -0
  51. package/dist/commands/pain-retry.d.ts.map +1 -1
  52. package/dist/commands/pain-retry.js +327 -32
  53. package/dist/commands/pain-retry.js.map +1 -1
  54. package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
  55. package/dist/commands/proven-channel-baseline.js +4 -2
  56. package/dist/commands/proven-channel-baseline.js.map +1 -1
  57. package/dist/commands/runtime-activation.d.ts +30 -0
  58. package/dist/commands/runtime-activation.d.ts.map +1 -1
  59. package/dist/commands/runtime-activation.js +567 -11
  60. package/dist/commands/runtime-activation.js.map +1 -1
  61. package/dist/commands/runtime-canary.d.ts.map +1 -1
  62. package/dist/commands/runtime-canary.js +32 -7
  63. package/dist/commands/runtime-canary.js.map +1 -1
  64. package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
  65. package/dist/commands/runtime-health-snapshot.js +4 -2
  66. package/dist/commands/runtime-health-snapshot.js.map +1 -1
  67. package/dist/commands/runtime-init.d.ts +43 -0
  68. package/dist/commands/runtime-init.d.ts.map +1 -0
  69. package/dist/commands/runtime-init.js +264 -0
  70. package/dist/commands/runtime-init.js.map +1 -0
  71. package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
  72. package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
  73. package/dist/commands/runtime-internalization-context-trace.js +374 -0
  74. package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
  75. package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  76. package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
  77. package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  78. package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  79. package/dist/commands/runtime-internalization-integrity.js +4 -2
  80. package/dist/commands/runtime-internalization-integrity.js.map +1 -1
  81. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  82. package/dist/commands/runtime-internalization-run-once.js +20 -5
  83. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  84. package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
  85. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  86. package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
  87. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  88. package/dist/commands/runtime-uat.d.ts.map +1 -1
  89. package/dist/commands/runtime-uat.guard.test.js +13 -15
  90. package/dist/commands/runtime-uat.guard.test.js.map +1 -1
  91. package/dist/commands/runtime-uat.js +4 -3
  92. package/dist/commands/runtime-uat.js.map +1 -1
  93. package/dist/config-reader.js +1 -1
  94. package/dist/config-reader.js.map +1 -1
  95. package/dist/index.js +54 -78
  96. package/dist/index.js.map +1 -1
  97. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
  98. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
  99. package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
  100. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
  101. package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
  102. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
  103. package/dist/services/config-doctor.d.ts.map +1 -1
  104. package/dist/services/config-doctor.js +9 -2
  105. package/dist/services/config-doctor.js.map +1 -1
  106. package/dist/services/console-launcher.d.ts +6 -0
  107. package/dist/services/console-launcher.d.ts.map +1 -1
  108. package/dist/services/console-launcher.js.map +1 -1
  109. package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
  110. package/dist/services/pain-flood-simulation-runner.js +9 -0
  111. package/dist/services/pain-flood-simulation-runner.js.map +1 -1
  112. package/dist/services/pd-config-loader.js +2 -2
  113. package/dist/services/pd-config-loader.js.map +1 -1
  114. package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
  115. package/dist/services/proven-channel-baseline-runner.js +10 -2
  116. package/dist/services/proven-channel-baseline-runner.js.map +1 -1
  117. package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
  118. package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
  119. package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
  120. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  121. package/dist/services/rulehost-pipeline-runner.js +111 -7
  122. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  123. package/dist/services/rulehost-readiness.js.map +1 -1
  124. package/dist/services/runtime-adapter-resolver.d.ts +1 -0
  125. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  126. package/dist/services/runtime-adapter-resolver.js +3 -0
  127. package/dist/services/runtime-adapter-resolver.js.map +1 -1
  128. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
  129. package/dist/services/synthetic-baseline-runner.js +11 -0
  130. package/dist/services/synthetic-baseline-runner.js.map +1 -1
  131. package/dist/utils/production-workspace-guard.d.ts +4 -4
  132. package/dist/utils/production-workspace-guard.d.ts.map +1 -1
  133. package/dist/utils/production-workspace-guard.js +19 -16
  134. package/dist/utils/production-workspace-guard.js.map +1 -1
  135. package/dist/utils/production-workspace-guard.test.js +54 -19
  136. package/dist/utils/production-workspace-guard.test.js.map +1 -1
  137. package/package.json +8 -5
  138. package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
  139. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
  140. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
  141. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
  142. package/src/commands/admission-gate.ts +30 -0
  143. package/src/commands/candidate.ts +74 -6
  144. package/src/commands/console.ts +41 -0
  145. package/src/commands/demo-story-a.ts +4 -2
  146. package/src/commands/diagnose.ts +158 -10
  147. package/src/commands/errors-list.ts +440 -0
  148. package/src/commands/health-codex.ts +249 -0
  149. package/src/commands/health.ts +60 -0
  150. package/src/commands/intent.ts +98 -29
  151. package/src/commands/pain-evidence.ts +6 -0
  152. package/src/commands/pain-record.ts +21 -0
  153. package/src/commands/pain-retry.ts +353 -35
  154. package/src/commands/proven-channel-baseline.ts +4 -2
  155. package/src/commands/runtime-activation.ts +657 -12
  156. package/src/commands/runtime-canary.ts +31 -5
  157. package/src/commands/runtime-health-snapshot.ts +4 -2
  158. package/src/commands/runtime-init.ts +305 -0
  159. package/src/commands/runtime-internalization-context-trace.ts +445 -0
  160. package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
  161. package/src/commands/runtime-internalization-integrity.ts +4 -2
  162. package/src/commands/runtime-internalization-run-once.ts +20 -5
  163. package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
  164. package/src/commands/runtime-uat.guard.test.ts +14 -16
  165. package/src/commands/runtime-uat.ts +4 -3
  166. package/src/config-reader.ts +1 -1
  167. package/src/index.ts +69 -78
  168. package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
  169. package/src/services/config-doctor.ts +9 -2
  170. package/src/services/console-launcher.ts +6 -0
  171. package/src/services/pain-flood-simulation-runner.ts +9 -0
  172. package/src/services/pd-config-loader.ts +2 -2
  173. package/src/services/proven-channel-baseline-runner.ts +10 -3
  174. package/src/services/rulehost-pipeline-runner.ts +157 -6
  175. package/src/services/runtime-adapter-resolver.ts +4 -0
  176. package/src/services/synthetic-baseline-runner.ts +11 -0
  177. package/src/utils/production-workspace-guard.test.ts +62 -20
  178. package/src/utils/production-workspace-guard.ts +19 -16
  179. package/tests/bdd/cli-contract.steps.ts +343 -0
  180. package/tests/bdd/context-trace.steps.ts +229 -0
  181. package/tests/bdd/support/gherkin-loader.ts +147 -0
  182. package/tests/bdd/support/repo-root.ts +62 -0
  183. package/tests/bdd/support/vitest-bdd.ts +179 -0
  184. package/tests/commands/admission-gate-coverage.test.ts +79 -0
  185. package/tests/commands/candidate-audit-repair.test.ts +77 -0
  186. package/tests/commands/candidate-intake.test.ts +155 -10
  187. package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
  188. package/tests/commands/cli-help-snapshot.test.ts +15 -1
  189. package/tests/commands/console-open.test.ts +229 -32
  190. package/tests/commands/diagnose.test.ts +619 -14
  191. package/tests/commands/errors-list.test.ts +678 -0
  192. package/tests/commands/health-codex.test.ts +243 -0
  193. package/tests/commands/health-host-wiring.test.ts +76 -0
  194. package/tests/commands/intent.test.ts +4 -4
  195. package/tests/commands/pain-record.test.ts +80 -0
  196. package/tests/commands/pain-retry.test.ts +94 -3
  197. package/tests/commands/run-rulehost-handler.test.ts +133 -0
  198. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
  199. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
  200. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
  201. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
  202. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
  203. package/tests/commands/runtime-activation.test.ts +1090 -5
  204. package/tests/commands/runtime-canary.test.ts +50 -0
  205. package/tests/commands/runtime-features.test.ts +2 -0
  206. package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
  207. package/tests/commands/runtime-init.test.ts +346 -0
  208. package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
  209. package/tests/commands/runtime-internalization.test.ts +2 -2
  210. package/tests/config-reader.test.ts +6 -5
  211. package/tests/e2e/cross-package-acceptance.test.ts +55 -13
  212. package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
  213. package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
  214. package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
  215. package/vitest.config.ts +1 -1
@@ -45,6 +45,11 @@ import {
45
45
  DEFAULT_MAX_ROUNDS,
46
46
  SqliteApprovalQueueStore,
47
47
  getChannelRiskLevel,
48
+ // PRI-510: feature-flag resolvers imported from the core barrel (EP-02:
49
+ // pd-config-loader.ts only re-exports loadPdConfig + computeFlagsFromLoadResult;
50
+ // computeFeatureFlagsFromConfig / isFeatureEnabled live in core).
51
+ computeFeatureFlagsFromConfig,
52
+ isFeatureEnabled,
48
53
  } from '@principles/core/runtime-v2';
49
54
  import type {
50
55
  AdversarialLoopResult,
@@ -53,10 +58,30 @@ import type {
53
58
  RefinerRuleHostGateDeps,
54
59
  PIArtifactStore,
55
60
  ApprovalRecord,
61
+ BehaviorExamplePack,
62
+ // PRI-510: type-only import for the repair-loop deps contract (EP-02 wiring).
63
+ // RuntimeStateManager / StoreEventEmitter are already imported as values
64
+ // above; EvaluatorValidator is type-only.
65
+ EvaluatorRunnerDeps,
66
+ SeedArtificerRepairParams,
67
+ EvaluatorValidator,
56
68
  } from '@principles/core/runtime-v2';
69
+ import { randomUUID, createHash } from 'node:crypto';
70
+ import { loadPdConfig } from './pd-config-loader.js';
57
71
  /* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after main, matching codebase convention */
58
72
  import { compileDemoRule } from './demo-rule-compiler.js';
59
73
 
74
+ /**
75
+ * Layer 0 content-hash function for the internalization progressive
76
+ * disclosure (design §6.1). Core never imports `node:crypto`, so the plugin/CLI
77
+ * layer injects the algorithm. Consumed only when the
78
+ * `artifact_summary_redundancy` flag is on; harmless otherwise.
79
+ *
80
+ * Exported so other CLI command modules (e.g. `runtime-internalization-run-once`)
81
+ * can reuse the same algorithm without each re-importing `node:crypto`.
82
+ */
83
+ export const contentHashFn = (input: string): string => createHash('sha256').update(input).digest('hex');
84
+
60
85
  // ── Types ────────────────────────────────────────────────────────────────────
61
86
 
62
87
  /**
@@ -113,6 +138,10 @@ export interface RuleHostPipelineOptions {
113
138
  * capability is treated as OFF with reason 'code_rule_capability not provided'.
114
139
  */
115
140
  readonly codeRuleCapability?: CodeRuleCapability;
141
+ /** Explicit Artificer contract selected by the workspace feature flag. */
142
+ readonly contextMode?: 'v1' | 'v2';
143
+ /** Required in v2 mode; assembled from Owner-labelled production evidence. */
144
+ readonly behaviorExamplePack?: BehaviorExamplePack;
116
145
  /** Internalization channel for created tasks (default 'code_tool_hook'). */
117
146
  readonly channel?: 'prompt' | 'code_tool_hook' | 'defer_archive';
118
147
  /** Max adversarial rounds (PRD cap = 2). */
@@ -286,7 +315,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
286
315
  stages.push({ name: 'dreamer', taskId: dreamerSeedTaskId, status: 'succeeded' });
287
316
  } else {
288
317
  const dreamerRunner = new DreamerRunner(
289
- { stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore },
318
+ { stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore, contentHashFn },
290
319
  runnerOptsFor(agentAdapters.dreamer),
291
320
  );
292
321
  const dreamerResult = await runStage(dreamerRunner, dreamerSeedTaskId, { maxStageRetries, pollIntervalMs });
@@ -303,7 +332,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
303
332
  const philosopherTaskId = `${correlation}-philosopher-${Date.now().toString(36)}`;
304
333
  await createInternalizationTask(stateManager, philosopherTaskId, 'philosopher', [dreamerSeedTaskId], channel, timeoutMs);
305
334
  const philosopherRunner = new PhilosopherRunner(
306
- { stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore },
335
+ { stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore, contentHashFn },
307
336
  runnerOptsFor(agentAdapters.philosopher),
308
337
  );
309
338
  const philosopherResult = await runStage(philosopherRunner, philosopherTaskId, { maxStageRetries, pollIntervalMs });
@@ -319,7 +348,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
319
348
  const scribeTaskId = `${correlation}-scribe-${Date.now().toString(36)}`;
320
349
  await createInternalizationTask(stateManager, scribeTaskId, 'scribe', [philosopherTaskId], channel, timeoutMs);
321
350
  const scribeRunner = new ScribeRunner(
322
- { stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore },
351
+ { stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore, contentHashFn },
323
352
  runnerOptsFor(agentAdapters.scribe),
324
353
  );
325
354
  const scribeResult = await runStage(scribeRunner, scribeTaskId, { maxStageRetries, pollIntervalMs });
@@ -352,11 +381,25 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
352
381
  }
353
382
  onProgress('adversarial_loop', 'start');
354
383
  const artificerRunner = new ArtificerRunner(
355
- { stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore },
384
+ {
385
+ stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
386
+ contextMode: opts.contextMode ?? 'v1', behaviorExamplePack: opts.behaviorExamplePack, contentHashFn,
387
+ },
356
388
  runnerOptsFor(capability.artificerAdapter),
357
389
  );
390
+ // PRI-510 (DEFECT-004): construct EvaluatorRunnerDeps via the centralized
391
+ // helper so the repair-loop wiring (isRepairLoopEnabled + seeder) is
392
+ // actually invoked in the production CLI path. EP-02: prior code passed
393
+ // only the 5 base deps, leaving the repair loop as dead code at runtime.
358
394
  const evaluatorRunner = new EvaluatorRunner(
359
- { stateManager, runtimeAdapter: agentAdapters.evaluator, eventEmitter, validator: new DefaultEvaluatorValidator(), artifactStore },
395
+ createEvaluatorRunnerDeps({
396
+ stateManager,
397
+ runtimeAdapter: agentAdapters.evaluator,
398
+ eventEmitter,
399
+ validator: new DefaultEvaluatorValidator(),
400
+ artifactStore,
401
+ workspaceDir: opts.workspaceDir,
402
+ }),
360
403
  { ...runnerOptsFor(agentAdapters.evaluator), gateDeps: createSandboxGateDeps() },
361
404
  );
362
405
 
@@ -405,7 +448,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
405
448
  // Enqueue failed — the candidate artifact exists but is not in the
406
449
  // approval queue. Degrade gracefully with a structured reason (ERR-002).
407
450
  const enqueueErr = err instanceof Error ? err.message : String(err);
408
- const degradeReason = `candidate_approved_but_enqueue_failed: ${enqueueErr}. Manual enqueue required: pd runtime activation dispatch --artifact-id ${loopResult.ruleArtifactId} --channel ${channel}`;
451
+ const degradeReason = `candidate_approved_but_enqueue_failed: ${enqueueErr}. Manual enqueue required: pd activation dispatch --artifact-id ${loopResult.ruleArtifactId} --channel ${channel}`;
409
452
  return {
410
453
  decision: pipelineDecision,
411
454
  painId: opts.painId,
@@ -528,6 +571,114 @@ async function createInternalizationTask(
528
571
  });
529
572
  }
530
573
 
574
+ // ── PRI-510 (DEFECT-004): EvaluatorRunner repair-loop CLI wiring ────────────
575
+ //
576
+ // EP-02 (Production Path Wiring): PRI-509 added the `isRepairLoopEnabled` and
577
+ // `seedArtificerRepairTask` deps to `EvaluatorRunnerDeps` (core), but the two
578
+ // CLI production paths (runtime-internalization-run-once.ts and this file)
579
+ // constructed `EvaluatorRunner` with only the base deps — the repair loop was
580
+ // dead code at runtime (evaluator needs_revision seeded nothing).
581
+ //
582
+ // This helper centralizes the construction of the full deps so both CLI sites
583
+ // stay in sync. The flag resolver reads `.pd/config.yaml` (the unified config
584
+ // — ADR-0016); the seeder writes the repair task via `stateManager.createTask`
585
+ // (the only sanctioned task-creation path; core peer runners are forbidden
586
+ // from calling it directly — architecture-regression.test.ts enforces this).
587
+
588
+ /**
589
+ * Inputs for {@link createEvaluatorRunnerDeps}. Carries the base `PeerRunnerDeps`
590
+ * fields plus `workspaceDir` (used to resolve the feature flag from
591
+ * `.pd/config.yaml`).
592
+ */
593
+ export interface CreateEvaluatorRunnerDepsInputs {
594
+ readonly stateManager: RuntimeStateManager;
595
+ readonly runtimeAdapter: PDRuntimeAdapter;
596
+ readonly eventEmitter: StoreEventEmitter;
597
+ readonly validator: EvaluatorValidator;
598
+ readonly artifactStore: PIArtifactStore;
599
+ /** Workspace directory containing `.pd/config.yaml` (flag source). */
600
+ readonly workspaceDir: string;
601
+ /**
602
+ * Layer 0 content-hash function (design §6.1). Optional — defaults to the
603
+ * module-level sha256-hex `contentHashFn`. Allow override for tests.
604
+ */
605
+ readonly contentHashFn?: (input: string) => string;
606
+ }
607
+
608
+ /**
609
+ * Build the full `EvaluatorRunnerDeps` for CLI production paths, including the
610
+ * PRI-509 repair-loop wiring (EP-02: production path must invoke core logic).
611
+ *
612
+ * - `isRepairLoopEnabled`: reads `.pd/config.yaml` and resolves the
613
+ * `evaluator_artificer_repair_loop` flag. Defaults to false when the config
614
+ * is missing or malformed (rc-9: fail safe — legacy path runs, never throws).
615
+ * - `seedArtificerRepairTask`: creates a new `artificer` task carrying the
616
+ * `repairPayload` in `diagnosticJson` (rc-1, rc-6: serialized via the
617
+ * validated `createPITaskDiagnosticJson`, no `as` bypass). Reuses the
618
+ * artificer runner (PRI-509 D1) rather than introducing a new task kind.
619
+ *
620
+ * Returns the newly created repair task's ID (UUIDv4, rc-7: fresh per call).
621
+ */
622
+ export function createEvaluatorRunnerDeps(inputs: CreateEvaluatorRunnerDepsInputs): EvaluatorRunnerDeps {
623
+ const { stateManager, runtimeAdapter, eventEmitter, validator, artifactStore, workspaceDir } = inputs;
624
+ return {
625
+ stateManager,
626
+ runtimeAdapter,
627
+ eventEmitter,
628
+ validator,
629
+ artifactStore,
630
+ // Layer 0 (design §6.1): inject the content-hash function so the evaluator
631
+ // writer can attach a `predecessorSummary.contentHash` for staleness
632
+ // detection. Defaults to the module-level sha256-hex constant.
633
+ contentHashFn: inputs.contentHashFn ?? contentHashFn,
634
+ isRepairLoopEnabled: (): boolean => {
635
+ // rc-9: never throw on malformed config — fail safe to false so the
636
+ // legacy (non-repair) path runs. The malformed config is already
637
+ // surfaced by `pd config doctor` and CLI start-up warnings.
638
+ try {
639
+ const result = loadPdConfig(workspaceDir);
640
+ const effective = result.ok ? result.effective : result.defaults;
641
+ const flags = computeFeatureFlagsFromConfig(effective);
642
+ return isFeatureEnabled(flags, 'evaluator_artificer_repair_loop');
643
+ } catch {
644
+ return false;
645
+ }
646
+ },
647
+ seedArtificerRepairTask: async (params: SeedArtificerRepairParams): Promise<string> => {
648
+ // rc-7: each call gets a fresh task ID — never reuse a cached ID.
649
+ const repairTaskId = `artificer-repair-${randomUUID()}`;
650
+ await stateManager.createTask({
651
+ taskId: repairTaskId,
652
+ // D1 (PRI-509): task kind is 'artificer' — reuses the artificer
653
+ // runner, which detects repairPayload in diagnosticJson and
654
+ // forwards it to the prompt builder as repairFeedback.
655
+ taskKind: 'artificer',
656
+ status: 'pending',
657
+ attemptCount: 0,
658
+ maxAttempts: 3,
659
+ // rc-1, rc-6: serialize via the validated helper — repairPayload
660
+ // is untrusted LLM output but `createPITaskDiagnosticJson` writes
661
+ // it through `serializePITaskMetadata`, which `parsePITaskMetadata`
662
+ // re-validates on read (defense in depth).
663
+ diagnosticJson: createPITaskDiagnosticJson({
664
+ // PITaskMetadata fields are mutable arrays (the metadata envelope
665
+ // serializes them as JSON arrays). The SeedArtificerRepairParams
666
+ // contract carries readonly arrays (rc-1: untrusted input from the
667
+ // evaluator LLM). Spread to a fresh mutable array — no mutation of
668
+ // the caller's data.
669
+ dependencyTaskIds: [...params.inheritedDependencyTaskIds],
670
+ channel: params.inheritedChannel,
671
+ timeoutMs: params.inheritedTimeoutMs,
672
+ inputArtifactRefs: [...params.inheritedInputArtifactRefs],
673
+ outputArtifactRefs: [],
674
+ repairPayload: params.repairPayload,
675
+ }),
676
+ });
677
+ return repairTaskId;
678
+ },
679
+ };
680
+ }
681
+
531
682
  /**
532
683
  * Run a single pipeline stage with bounded retry for transient `retried` status.
533
684
  *
@@ -103,6 +103,7 @@ export interface ResolveAdapterOptions {
103
103
  apiKeyEnv?: string;
104
104
  baseUrl?: string;
105
105
  maxRetries?: number;
106
+ maxTokens?: number;
106
107
  };
107
108
  /**
108
109
  * CLI override for openclaw mode (from --openclaw-local / --openclaw-gateway flags).
@@ -215,6 +216,7 @@ export function resolveRuntimeAdapterFromConfig(opts: ResolveAdapterOptions): PD
215
216
  const apiKeyEnv = opts.piAiOverrides?.apiKeyEnv ?? configFields.apiKeyEnv;
216
217
  const baseUrl = opts.piAiOverrides?.baseUrl ?? configFields.baseUrl;
217
218
  const maxRetries = opts.piAiOverrides?.maxRetries ?? configFields.maxRetries;
219
+ const maxTokens = opts.piAiOverrides?.maxTokens ?? configFields.maxTokens;
218
220
  const adapterTimeoutMs = opts.timeoutMs ?? configFields.timeoutMs;
219
221
 
220
222
  // PRI-431 Step 1d + PR review fix: manual missing-field check on MERGED values
@@ -283,9 +285,11 @@ export function resolveRuntimeAdapterFromConfig(opts: ResolveAdapterOptions): PD
283
285
  model: String(model),
284
286
  apiKeyEnv: String(apiKeyEnv),
285
287
  maxRetries,
288
+ maxTokens,
286
289
  timeoutMs: adapterTimeoutMs,
287
290
  baseUrl,
288
291
  workspace: opts.workspaceDir,
292
+ ...(configFields.systemPrompt ? { systemPrompt: configFields.systemPrompt } : {}),
289
293
  });
290
294
  }
291
295
 
@@ -21,6 +21,7 @@ import { auditCandidateLedgerConsistency } from '@principles/core/runtime-v2';
21
21
  import { OperatorHealthReadModel } from '@principles/core/runtime-v2';
22
22
  import { createInternalizationQueueReadModel } from '@principles/core/runtime-v2';
23
23
  import { createPITaskDiagnosticJson } from '@principles/core/runtime-v2';
24
+ import { contentHashFn } from './rulehost-pipeline-runner.js';
24
25
  import {
25
26
  computeOverallStatus,
26
27
  boundedEvidence,
@@ -148,6 +149,7 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
148
149
  artifactStore: stateManager.piArtifactStore,
149
150
  validator: new DefaultDiagRootCauseValidator(),
150
151
  contextAssembler,
152
+ contentHashFn,
151
153
  },
152
154
  {
153
155
  owner: 'synthetic-baseline',
@@ -164,6 +166,7 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
164
166
  eventEmitter,
165
167
  artifactStore: stateManager.piArtifactStore,
166
168
  validator: new DefaultDiagDistillerValidator(),
169
+ contentHashFn,
167
170
  },
168
171
  {
169
172
  owner: 'synthetic-baseline',
@@ -180,6 +183,7 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
180
183
  eventEmitter,
181
184
  artifactStore: stateManager.piArtifactStore,
182
185
  committer,
186
+ contentHashFn,
183
187
  },
184
188
  {
185
189
  owner: 'synthetic-baseline',
@@ -214,6 +218,13 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
214
218
  painType: 'tool_failure',
215
219
  source: 'synthetic-baseline',
216
220
  reason: 'Synthetic baseline deterministic pain signal',
221
+ // Synthetic baseline is a deterministic smoke tool — it has no real host
222
+ // session to harvest evidence from. Provide a synthetic evidence entry so
223
+ // the PEAT-B1 admission gate (painEvidenceAdmission, default-on) does not
224
+ // short-circuit the pain before diagnosis. Without this, the baseline
225
+ // reports `short_circuited: input evidence empty` and every downstream
226
+ // stage fails, even though the pipeline itself is healthy.
227
+ evidence: [{ sourceRef: `synthetic:${painId}`, note: 'Synthetic baseline deterministic evidence anchor' }],
217
228
  });
218
229
 
219
230
  const painPassed = bridgeResult.status === 'succeeded';
@@ -5,23 +5,27 @@ import {
5
5
  isProductionWorkspace,
6
6
  guardUatWorkspace,
7
7
  getSafeUatWorkspacePath,
8
+ resolveWorkspacePath,
8
9
  formatGuardRefusal,
9
10
  type GuardRefusal,
10
11
  } from './production-workspace-guard.js';
11
12
 
13
+ // Use os.homedir()-derived paths so the tests work cross-platform
14
+ // (previously hardcoded Windows paths like D:\.openclaw\workspace which on
15
+ // Linux resolve to <cwd>/D:\.openclaw\workspace — meaningless).
16
+ const HOMEDIR_WORKSPACE = path.join(os.homedir(), '.openclaw', 'workspace');
17
+
12
18
  const PRODUCTION_PATHS = [
13
- 'D:\\.openclaw\\workspace',
14
- 'C:\\.openclaw\\workspace',
15
- 'C:\\Users\\Administrator\\.openclaw\\workspace',
16
- path.join(os.homedir(), '.openclaw', 'workspace'),
19
+ HOMEDIR_WORKSPACE,
17
20
  ];
18
21
 
19
22
  const SAFE_PATHS = [
20
- 'D:\\.openclaw\\workspace-test',
21
- 'D:\\.openclaw\\workspace-backup',
23
+ path.join(os.homedir(), '.openclaw', 'workspace-test'),
24
+ path.join(os.homedir(), '.openclaw', 'workspace-backup'),
22
25
  path.join(os.tmpdir(), 'pd-uat-workspace'),
23
26
  path.join(os.tmpdir(), 'pd-test-any'),
24
- 'C:\\completely-unrelated\\work',
27
+ // A path that is clearly unrelated on every platform
28
+ path.join(os.tmpdir(), 'completely-unrelated-work'),
25
29
  ];
26
30
 
27
31
  describe('isProductionWorkspace', () => {
@@ -32,18 +36,15 @@ describe('isProductionWorkspace', () => {
32
36
  expect(isProductionWorkspace(path.resolve(safePath))).toBe(false);
33
37
  });
34
38
  it('detects descendant', () => {
35
- // Use a platform-appropriate descendant path so the test works on both
36
- // Windows (where D:\... resolves as-is) and Linux CI (where D:\... is
37
- // treated as a relative path and resolves under cwd).
38
- const prodPath = path.resolve(path.join(os.homedir(), '.openclaw', 'workspace'));
39
+ const prodPath = path.resolve(HOMEDIR_WORKSPACE);
39
40
  const descendantPath = path.join(prodPath, 'sub', 'child');
40
41
  expect(isProductionWorkspace(descendantPath)).toBe(true);
41
42
  });
42
43
  it('rejects sibling workspace-test (ERR-030)', () => {
43
- expect(isProductionWorkspace(path.resolve('D:\\.openclaw\\workspace-test'))).toBe(false);
44
+ expect(isProductionWorkspace(path.resolve(path.join(os.homedir(), '.openclaw', 'workspace-test')))).toBe(false);
44
45
  });
45
46
  it('rejects sibling workspace-backup (ERR-030)', () => {
46
- expect(isProductionWorkspace(path.resolve('D:\\.openclaw\\workspace-backup'))).toBe(false);
47
+ expect(isProductionWorkspace(path.resolve(path.join(os.homedir(), '.openclaw', 'workspace-backup')))).toBe(false);
47
48
  });
48
49
  });
49
50
 
@@ -58,7 +59,7 @@ describe('guardUatWorkspace', () => {
58
59
  }
59
60
  });
60
61
  it('refuses descendant', () => {
61
- const prodPath = path.resolve(path.join(os.homedir(), '.openclaw', 'workspace'));
62
+ const prodPath = path.resolve(HOMEDIR_WORKSPACE);
62
63
  const descendantPath = path.join(prodPath, 'subdir');
63
64
  expect(guardUatWorkspace(descendantPath, 'test').refused).toBe(true);
64
65
  });
@@ -68,8 +69,10 @@ describe('guardUatWorkspace', () => {
68
69
  const r = guardUatWorkspace(safePath, 'test');
69
70
  expect(r.refused).toBe(false);
70
71
  });
71
- it.each(['D:\\.openclaw\\workspace-test', 'D:\\.openclaw\\workspace-backup'])(
72
- 'allows sibling: %s (ERR-030)', (p) => {
72
+ it.each([
73
+ path.join(os.homedir(), '.openclaw', 'workspace-test'),
74
+ path.join(os.homedir(), '.openclaw', 'workspace-backup'),
75
+ ])('allows sibling: %s (ERR-030)', (p) => {
73
76
  expect(guardUatWorkspace(p, 'test').refused).toBe(false);
74
77
  });
75
78
  });
@@ -77,7 +80,7 @@ describe('guardUatWorkspace', () => {
77
80
 
78
81
  describe('JSON output (EP-04)', () => {
79
82
  it('outputs single object with reason and nextAction', () => {
80
- const r = guardUatWorkspace('D:\\.openclaw\\workspace', 'test');
83
+ const r = guardUatWorkspace(HOMEDIR_WORKSPACE, 'test');
81
84
  const json = formatGuardRefusal(r as GuardRefusal, 'test', true);
82
85
  const parsed = JSON.parse(json);
83
86
  expect(parsed).toMatchObject({
@@ -87,7 +90,7 @@ describe('JSON output (EP-04)', () => {
87
90
  expect(Array.isArray(parsed)).toBe(false);
88
91
  });
89
92
  it('no console prefixes in JSON', () => {
90
- const r = guardUatWorkspace('D:\\.openclaw\\workspace', 'test');
93
+ const r = guardUatWorkspace(HOMEDIR_WORKSPACE, 'test');
91
94
  const json = formatGuardRefusal(r as GuardRefusal, 'test', true);
92
95
  expect(json).not.toContain('[pd-cli]');
93
96
  expect(json).not.toContain('ERROR:');
@@ -97,7 +100,7 @@ describe('JSON output (EP-04)', () => {
97
100
 
98
101
  describe('text output (EP-03)', () => {
99
102
  it('includes reason and nextAction', () => {
100
- const r = guardUatWorkspace('D:\\.openclaw\\workspace', 'test');
103
+ const r = guardUatWorkspace(HOMEDIR_WORKSPACE, 'test');
101
104
  const text = formatGuardRefusal(r as GuardRefusal, 'test', false);
102
105
  expect(text).toContain('Reason:');
103
106
  expect(text).toContain('Next Action:');
@@ -112,4 +115,43 @@ describe('getSafeUatWorkspacePath', () => {
112
115
  expect(p1).toContain(os.tmpdir());
113
116
  expect(p1).toContain('pd-uat-workspace');
114
117
  });
115
- });
118
+ });
119
+
120
+ describe('resolveWorkspacePath', () => {
121
+ it('resolves an absolute path as-is', () => {
122
+ const abs = path.resolve('/tmp/my-workspace');
123
+ expect(resolveWorkspacePath(abs)).toBe(abs);
124
+ });
125
+
126
+ it('resolves a relative path against cwd', () => {
127
+ const result = resolveWorkspacePath('relative/path');
128
+ const expected = path.resolve(process.cwd(), 'relative/path');
129
+ expect(result).toBe(expected);
130
+ });
131
+
132
+ it('defaults to cwd when no input is provided', () => {
133
+ const result = resolveWorkspacePath(undefined);
134
+ expect(result).toBe(path.resolve(process.cwd()));
135
+ });
136
+ });
137
+
138
+ describe('PD_PRODUCTION_WORKSPACE env var (module-load-time)', () => {
139
+ // NOTE: PRODUCTION_WORKSPACE_PATHS is built once at module load time from
140
+ // process.env.PD_PRODUCTION_WORKSPACE. Setting the env var after the module
141
+ // is loaded does NOT change behavior. These tests verify that the default
142
+ // production path (HOMEDIR_WORKSPACE) is correctly protected, which is the
143
+ // primary regression risk.
144
+
145
+ it('guardUatWorkspace refuses the default production workspace', () => {
146
+ const r = guardUatWorkspace(HOMEDIR_WORKSPACE, 'test');
147
+ expect(r.refused).toBe(true);
148
+ if (r.refused) {
149
+ expect(r.isProduction).toBe(true);
150
+ expect(r.workspace).toBe(path.resolve(HOMEDIR_WORKSPACE));
151
+ }
152
+ });
153
+
154
+ it('isProductionWorkspace returns true for the default production path', () => {
155
+ expect(isProductionWorkspace(path.resolve(HOMEDIR_WORKSPACE))).toBe(true);
156
+ });
157
+ });
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * Prevents UAT/runtime test commands from writing to production workspaces.
5
5
  *
6
- * Production workspaces are:
7
- * - D:\.openclaw\workspace
8
- * - C:\Users\Administrator\.openclaw\workspace
9
- * - And the default workspace resolved by OpenClaw configuration
6
+ * Production workspaces:
7
+ * - Default: ~/.openclaw/workspace (cross-platform)
8
+ * - Additional paths declared via PD_PRODUCTION_WORKSPACE env var
9
+ * (use path.delimiter `;` on Windows, `:` on Unix — to separate multiple paths)
10
10
  *
11
11
  * This module follows ERR-030 (path prefix matching must use segment boundaries)
12
12
  * and EP-03/EP-04 (fail loud with structured reason and nextAction).
@@ -20,19 +20,22 @@ import { existsSync } from 'fs';
20
20
 
21
21
  /**
22
22
  * List of production workspace paths that should be protected from UAT/test writes.
23
- * These are the default paths where PD is typically installed and used for real work.
23
+ * Built from: the universal default (~/.openclaw/workspace) plus any paths
24
+ * declared via PD_PRODUCTION_WORKSPACE.
24
25
  */
25
- const PRODUCTION_WORKSPACE_PATHS = [
26
- // Windows default
27
- path.resolve('D:\\.openclaw\\workspace'),
28
- path.resolve('C:\\.openclaw\\workspace'),
29
- path.resolve('C:\\Users\\Administrator\\.openclaw\\workspace'),
30
- path.resolve('C:\\Users\\Admin\\.openclaw\\workspace'),
31
- // Unix-like defaults
32
- path.resolve(path.join(os.homedir(), '.openclaw', 'workspace')),
33
- // macOS-specific
34
- path.resolve(path.join(os.homedir(), '.openclaw', 'workspace')),
35
- ];
26
+ function buildProductionWorkspacePaths(): string[] {
27
+ const paths = [path.resolve(path.join(os.homedir(), '.openclaw', 'workspace'))];
28
+ const extra = process.env.PD_PRODUCTION_WORKSPACE;
29
+ if (extra) {
30
+ for (const p of extra.split(path.delimiter)) {
31
+ const trimmed = p.trim();
32
+ if (trimmed) paths.push(path.resolve(trimmed));
33
+ }
34
+ }
35
+ return paths;
36
+ }
37
+
38
+ const PRODUCTION_WORKSPACE_PATHS = buildProductionWorkspacePaths();
36
39
 
37
40
  // ── Resolution helpers ───────────────────────────────────────────────────────
38
41