@principles/pd-cli 1.132.0 → 1.134.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 (212) 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 +40 -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/pain-flood-simulation-runner.d.ts.map +1 -1
  107. package/dist/services/pain-flood-simulation-runner.js +9 -0
  108. package/dist/services/pain-flood-simulation-runner.js.map +1 -1
  109. package/dist/services/pd-config-loader.js +2 -2
  110. package/dist/services/pd-config-loader.js.map +1 -1
  111. package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
  112. package/dist/services/proven-channel-baseline-runner.js +10 -2
  113. package/dist/services/proven-channel-baseline-runner.js.map +1 -1
  114. package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
  115. package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
  116. package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
  117. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  118. package/dist/services/rulehost-pipeline-runner.js +111 -7
  119. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  120. package/dist/services/rulehost-readiness.js.map +1 -1
  121. package/dist/services/runtime-adapter-resolver.d.ts +1 -0
  122. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  123. package/dist/services/runtime-adapter-resolver.js +3 -0
  124. package/dist/services/runtime-adapter-resolver.js.map +1 -1
  125. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
  126. package/dist/services/synthetic-baseline-runner.js +11 -0
  127. package/dist/services/synthetic-baseline-runner.js.map +1 -1
  128. package/dist/utils/production-workspace-guard.d.ts +4 -4
  129. package/dist/utils/production-workspace-guard.d.ts.map +1 -1
  130. package/dist/utils/production-workspace-guard.js +19 -16
  131. package/dist/utils/production-workspace-guard.js.map +1 -1
  132. package/dist/utils/production-workspace-guard.test.js +54 -19
  133. package/dist/utils/production-workspace-guard.test.js.map +1 -1
  134. package/package.json +8 -5
  135. package/scripts/migrate-illegal-expected-decision.ts +252 -0
  136. package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
  137. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
  138. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
  139. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
  140. package/src/commands/admission-gate.ts +30 -0
  141. package/src/commands/candidate.ts +74 -6
  142. package/src/commands/console.ts +40 -0
  143. package/src/commands/demo-story-a.ts +4 -2
  144. package/src/commands/diagnose.ts +158 -10
  145. package/src/commands/errors-list.ts +440 -0
  146. package/src/commands/health-codex.ts +249 -0
  147. package/src/commands/health.ts +60 -0
  148. package/src/commands/intent.ts +98 -29
  149. package/src/commands/pain-evidence.ts +6 -0
  150. package/src/commands/pain-record.ts +21 -0
  151. package/src/commands/pain-retry.ts +353 -35
  152. package/src/commands/proven-channel-baseline.ts +4 -2
  153. package/src/commands/runtime-activation.ts +657 -12
  154. package/src/commands/runtime-canary.ts +31 -5
  155. package/src/commands/runtime-health-snapshot.ts +4 -2
  156. package/src/commands/runtime-init.ts +305 -0
  157. package/src/commands/runtime-internalization-context-trace.ts +445 -0
  158. package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
  159. package/src/commands/runtime-internalization-integrity.ts +4 -2
  160. package/src/commands/runtime-internalization-run-once.ts +20 -5
  161. package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
  162. package/src/commands/runtime-uat.guard.test.ts +14 -16
  163. package/src/commands/runtime-uat.ts +4 -3
  164. package/src/config-reader.ts +1 -1
  165. package/src/index.ts +69 -78
  166. package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
  167. package/src/services/config-doctor.ts +9 -2
  168. package/src/services/pain-flood-simulation-runner.ts +9 -0
  169. package/src/services/pd-config-loader.ts +2 -2
  170. package/src/services/proven-channel-baseline-runner.ts +10 -3
  171. package/src/services/rulehost-pipeline-runner.ts +157 -6
  172. package/src/services/runtime-adapter-resolver.ts +4 -0
  173. package/src/services/synthetic-baseline-runner.ts +11 -0
  174. package/src/utils/production-workspace-guard.test.ts +62 -20
  175. package/src/utils/production-workspace-guard.ts +19 -16
  176. package/tests/bdd/cli-contract.steps.ts +343 -0
  177. package/tests/bdd/context-trace.steps.ts +229 -0
  178. package/tests/bdd/support/gherkin-loader.ts +147 -0
  179. package/tests/bdd/support/repo-root.ts +62 -0
  180. package/tests/bdd/support/vitest-bdd.ts +179 -0
  181. package/tests/commands/admission-gate-coverage.test.ts +79 -0
  182. package/tests/commands/candidate-audit-repair.test.ts +77 -0
  183. package/tests/commands/candidate-intake.test.ts +155 -10
  184. package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
  185. package/tests/commands/cli-help-snapshot.test.ts +15 -1
  186. package/tests/commands/console-open.test.ts +19 -0
  187. package/tests/commands/diagnose.test.ts +619 -14
  188. package/tests/commands/errors-list.test.ts +678 -0
  189. package/tests/commands/health-codex.test.ts +243 -0
  190. package/tests/commands/health-host-wiring.test.ts +76 -0
  191. package/tests/commands/intent.test.ts +4 -4
  192. package/tests/commands/pain-record.test.ts +80 -0
  193. package/tests/commands/pain-retry.test.ts +94 -3
  194. package/tests/commands/run-rulehost-handler.test.ts +133 -0
  195. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
  196. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
  197. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
  198. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
  199. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
  200. package/tests/commands/runtime-activation.test.ts +1090 -5
  201. package/tests/commands/runtime-canary.test.ts +50 -0
  202. package/tests/commands/runtime-features.test.ts +2 -0
  203. package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
  204. package/tests/commands/runtime-init.test.ts +346 -0
  205. package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
  206. package/tests/commands/runtime-internalization.test.ts +2 -2
  207. package/tests/config-reader.test.ts +6 -5
  208. package/tests/e2e/cross-package-acceptance.test.ts +55 -13
  209. package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
  210. package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
  211. package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
  212. package/vitest.config.ts +1 -1
@@ -27,6 +27,7 @@ import { loadLedger, getLedgerFilePathPublic } from '@principles/core/principle-
27
27
  import { resolveWorkspaceDir } from '../resolve-workspace.js';
28
28
  import { createRemediationResult, remediationAction } from './remediation-output.js';
29
29
  import type { RemediationResult } from './remediation-output.js';
30
+ import { checkAdmissionGate } from './admission-gate.js';
30
31
 
31
32
  // ── Interfaces ────────────────────────────────────────────────────────────────
32
33
 
@@ -353,6 +354,7 @@ export async function handleCandidateInternalize(opts: CandidateInternalizeOptio
353
354
  }
354
355
 
355
356
  const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
357
+ // eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
356
358
  if ('decision' in seed) {
357
359
  const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
358
360
  const reason = decisionResult.decision === 'already_exists'
@@ -494,6 +496,13 @@ export async function handleCandidateShow(opts: CandidateShowOptions): Promise<v
494
496
  if (result.ledgerEntryId) {
495
497
  console.log(` Ledger Entry: ${result.ledgerEntryId}`);
496
498
  }
499
+ // F10-1: surface dangling sourceRunId warning in text mode (rc-9).
500
+ if (result.warning) {
501
+ console.log(` ⚠ WARNING: ${result.warning}`);
502
+ }
503
+ if (result.nextAction) {
504
+ console.log(` Next Action: ${result.nextAction}`);
505
+ }
497
506
  console.log('');
498
507
  } finally {
499
508
  await stateManager.close();
@@ -520,12 +529,37 @@ export async function handleCandidateIntake(opts: CandidateIntakeOptions): Promi
520
529
  const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
521
530
  const service = new CandidateIntakeService({ stateManager, ledgerAdapter });
522
531
 
523
- if (opts.dryRun) {
524
- const candidate = await stateManager.getCandidate(opts.candidateId);
525
- if (!candidate) {
526
- console.error(`Candidate not found: ${opts.candidateId}`);
527
- process.exit(1);
532
+ // Fetch candidate once for admission gate check and downstream paths.
533
+ const candidate = await stateManager.getCandidate(opts.candidateId);
534
+ if (!candidate) {
535
+ console.error(`Candidate not found: ${opts.candidateId}`);
536
+ process.exit(1);
537
+ return;
538
+ }
539
+
540
+ // PRI-442 Stage 4: admission gate check — refuse non-admitted candidates
541
+ // to prevent CLI from bypassing the production admission gate.
542
+ const admissionBlock = checkAdmissionGate(candidate);
543
+ if (admissionBlock) {
544
+ const result = {
545
+ candidateId: opts.candidateId,
546
+ status: 'refused',
547
+ admissionDecision: admissionBlock.decision,
548
+ reason: admissionBlock.reason,
549
+ nextAction: admissionBlock.nextAction,
550
+ };
551
+ if (opts.json) {
552
+ console.log(JSON.stringify(result, null, 2));
553
+ } else {
554
+ console.error(`Admission gate refused candidate ${opts.candidateId}: ${admissionBlock.decision}`);
555
+ console.error(` Reason: ${admissionBlock.reason}`);
556
+ console.error(` Next Action: ${admissionBlock.nextAction}`);
528
557
  }
558
+ process.exit(1);
559
+ return;
560
+ }
561
+
562
+ if (opts.dryRun) {
529
563
  const artifact = await stateManager.getArtifact(candidate.artifactId);
530
564
  if (!artifact) {
531
565
  console.error(`Artifact not found for candidate: ${opts.candidateId}`);
@@ -564,7 +598,6 @@ export async function handleCandidateIntake(opts: CandidateIntakeOptions): Promi
564
598
  const entry = await service.intake(opts.candidateId);
565
599
 
566
600
  // Check if already consumed before this call
567
- const candidate = await stateManager.getCandidate(opts.candidateId);
568
601
  if (candidate?.status === 'consumed') {
569
602
  const infoMessage = `Candidate ${opts.candidateId} was already consumed. Ledger entry: ${entry.id}`;
570
603
  if (opts.json) {
@@ -724,6 +757,30 @@ export async function handleCandidateRepair(opts: CandidateRepairOptions): Promi
724
757
  process.exit(1);
725
758
  }
726
759
 
760
+ // PRI-442 Stage 4: admission gate check — even for consumed candidates,
761
+ // re-verify admission before re-intaking to prevent bypassing the gate.
762
+ // A consumed candidate that would fail the gate indicates data corruption
763
+ // or a prior bypass; refuse and surface the reason.
764
+ const admissionBlock = checkAdmissionGate(candidate);
765
+ if (admissionBlock) {
766
+ const result = {
767
+ candidateId: opts.candidateId,
768
+ status: 'refused',
769
+ admissionDecision: admissionBlock.decision,
770
+ reason: admissionBlock.reason,
771
+ nextAction: admissionBlock.nextAction,
772
+ };
773
+ if (opts.json) {
774
+ console.log(JSON.stringify(result, null, 2));
775
+ } else {
776
+ console.error(`Admission gate refused candidate ${opts.candidateId}: ${admissionBlock.decision}`);
777
+ console.error(` Reason: ${admissionBlock.reason}`);
778
+ console.error(` Next Action: ${admissionBlock.nextAction}`);
779
+ }
780
+ process.exit(1);
781
+ return;
782
+ }
783
+
727
784
  const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
728
785
  const service = new CandidateIntakeService({ stateManager, ledgerAdapter });
729
786
 
@@ -931,6 +988,7 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
931
988
  continue;
932
989
  }
933
990
  const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
991
+ // eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
934
992
  if ('decision' in seed) {
935
993
  output.deferred++;
936
994
  const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
@@ -998,6 +1056,15 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
998
1056
  continue;
999
1057
  }
1000
1058
 
1059
+ // PRI-442 Stage 4: admission gate check — refuse non-admitted pending
1060
+ // candidates to prevent backfill from bypassing the admission gate.
1061
+ const admissionBlock = checkAdmissionGate(candidate);
1062
+ if (admissionBlock) {
1063
+ output.intakeFailed++;
1064
+ output.results.push({ candidateId, route: 'unknown', status: 'intake_failed', reason: `Admission gate refused: ${admissionBlock.reason}`, statusBefore: 'pending', statusAfter: 'pending', intakeDecision: 'skipped', seedDecision: 'skipped', nextAction: admissionBlock.nextAction });
1065
+ continue;
1066
+ }
1067
+
1001
1068
  const recommendation = resolveCandidateRecommendation(candidate, stateManager, candidateId);
1002
1069
  const decision = decideInternalizationRoute(recommendation as Parameters<typeof decideInternalizationRoute>[0]);
1003
1070
 
@@ -1010,6 +1077,7 @@ export async function handleCandidateInternalizationBackfill(opts: CandidateBack
1010
1077
  continue;
1011
1078
  }
1012
1079
  const seed = buildDreamerSeedFromCandidate(candidate, { route: decision.route, ready: decision.ready, sourcePainId });
1080
+ // eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
1013
1081
  if ('decision' in seed) {
1014
1082
  output.deferred++;
1015
1083
  const decisionResult = seed as { decision: string; reason?: string; taskId?: string };
@@ -72,6 +72,20 @@ export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
72
72
  return;
73
73
  }
74
74
 
75
+ // EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
76
+ // Without dist/web/index.html the server returns 404 "Run npm run build:ui first".
77
+ const webIndex = path.join(consoleDir, 'dist', 'web', 'index.html');
78
+ if (!fs.existsSync(webIndex)) {
79
+ const msg = `Console web UI not found at ${webIndex}. The console bundle is corrupted. Re-run installer.`;
80
+ if (opts.json) {
81
+ console.log(JSON.stringify({ success: false, reason: msg, nextAction: 'npx create-principles-disciple' }));
82
+ } else {
83
+ console.error(msg);
84
+ }
85
+ process.exit(1);
86
+ return;
87
+ }
88
+
75
89
  const port = opts.port || '3100';
76
90
  const host = '127.0.0.1';
77
91
  const args = [serverEntry, '--workspace', workspaceDir, '--port', port, '--host', host];
@@ -305,6 +319,32 @@ export async function handleConsoleOpen(opts: ConsoleOpenOptions = {}): Promise<
305
319
  return;
306
320
  }
307
321
 
322
+ // EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
323
+ // Without dist/web/index.html the server returns 404 "Run npm run build:ui first"
324
+ // — a fatal first-impression bug for new users.
325
+ const webIndex = path.join(consoleDir, 'dist', 'web', 'index.html');
326
+ if (!fs.existsSync(webIndex)) {
327
+ const result: ConsoleLaunchResult = {
328
+ status: 'failed',
329
+ url: '',
330
+ port: 0,
331
+ host: '127.0.0.1',
332
+ workspaceDir,
333
+ reused: false,
334
+ browserOpened: false,
335
+ reason: 'console_web_ui_missing',
336
+ nextAction: `Re-run installer: npx create-principles-disciple (expected ${webIndex})`,
337
+ };
338
+ if (opts.json) {
339
+ console.log(JSON.stringify(result, null, 2));
340
+ } else {
341
+ console.error(`error: ${result.reason}`);
342
+ console.error(`next: ${result.nextAction}`);
343
+ }
344
+ process.exit(1);
345
+ return;
346
+ }
347
+
308
348
  // 4) Read auth token for health probes (PD_CONSOLE_TOKEN)
309
349
  const token = opts.token ?? process.env.PD_CONSOLE_TOKEN;
310
350
 
@@ -183,8 +183,10 @@ export async function handleDemoStoryA(opts: DemoStoryAOptions): Promise<void> {
183
183
  }
184
184
 
185
185
  if (result.status !== 'passed') {
186
- console.error('');
187
- console.error(`FAIL: status=${result.status}`);
186
+ if (!opts.json) {
187
+ console.error('');
188
+ console.error(`FAIL: status=${result.status}`);
189
+ }
188
190
  process.exitCode = 1;
189
191
  }
190
192
  } finally {
@@ -29,13 +29,23 @@ import {
29
29
  run as diagnoseRun,
30
30
  status as diagnoseStatus,
31
31
  PrincipleTreeLedgerAdapter,
32
+ buildDreamerSeedFromCandidate,
33
+ CANDIDATE_KIND_TO_ROUTE,
34
+ ROUTE_CHANNEL_MAP,
35
+ MVP_ENABLED_CHANNELS,
32
36
  } from '@principles/core/runtime-v2';
33
37
  import type { PDRuntimeAdapter, OutputLanguage } from '@principles/core/runtime-v2';
34
38
  import { resolveWorkspaceDir } from '../resolve-workspace.js';
35
39
  import { readOutputLanguageFromWorkspace } from '../config-reader.js';
36
40
  import { loadPdConfig, computeFlagsFromLoadResult } from '../services/pd-config-loader.js';
37
41
  import { isFeatureEnabled, SPLIT_PIPELINE_TOTAL_TIMEOUT_MS } from '@principles/core/runtime-v2';
42
+ import { createHash } from 'node:crypto';
43
+ /** Layer 0 content-hash (design §6.1); injected so diag writers can attach predecessorSummary hashes. */
44
+ const contentHashFn = (input: string): string => createHash('sha256').update(input).digest('hex');
38
45
  import { resolveRuntimeAdapterFromConfig, ConfigResolutionError } from '../services/runtime-adapter-resolver.js';
46
+ import { resolveRuntimeFromPdConfig } from '../services/resolve-runtime-from-pd-config.js';
47
+ import { checkAdmissionGate } from './admission-gate.js';
48
+ import { resolveSourcePainIdFromDiagnostician } from './candidate.js';
39
49
  import * as path from 'path';
40
50
 
41
51
  function validateStalledThreshold(val: unknown): number | undefined {
@@ -198,7 +208,38 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
198
208
  return;
199
209
  }
200
210
 
201
- const runtimeKind = opts.runtime ?? 'test-double';
211
+ // Resolve runtime kind. P1 fix (mirrors pain-retry.ts): pd diagnose run
212
+ // must NOT default to test-double. Without --runtime, the split pipeline
213
+ // would validate the test-double's stale DiagnosticianOutputV1-shaped
214
+ // payload against DiagRootCauseOutputV1Schema and fail with
215
+ // max_attempts_exceeded — silently producing failed/fake diagnostic data
216
+ // in a real workspace. Fall back to .pd/config.yaml first; refuse if
217
+ // nothing is bound. (rc-9-no-silent-fallback / ERR-002; EP-03, EP-04)
218
+ let runtimeKind = opts.runtime;
219
+ if (!runtimeKind) {
220
+ const resolved = resolveRuntimeFromPdConfig(workspaceDir);
221
+ if (!isRuntimeConfigError(resolved.result) && resolved.result.runtimeKind) {
222
+ ({ runtimeKind } = resolved.result);
223
+ }
224
+ }
225
+
226
+ if (!runtimeKind) {
227
+ const missingRuntime = {
228
+ ok: false,
229
+ reason: 'missing_runtime',
230
+ message:
231
+ 'No --runtime specified and no .pd/config.yaml runtime binding found. pd diagnose run must not default to test-double to prevent fake data in real workspaces.',
232
+ nextAction: `Specify --runtime explicitly: pd diagnose run --task-id ${opts.taskId} --runtime pi-ai --provider <provider> --model <model> --apiKeyEnv <ENV>`,
233
+ };
234
+ if (opts.json) {
235
+ console.log(JSON.stringify(missingRuntime, null, 2));
236
+ } else {
237
+ console.error(`error: ${missingRuntime.message}`);
238
+ console.error(`Next action: ${missingRuntime.nextAction}`);
239
+ }
240
+ process.exit(1);
241
+ return;
242
+ }
202
243
 
203
244
  const stateManager = new RuntimeStateManager({ workspaceDir });
204
245
 
@@ -353,22 +394,37 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
353
394
  const featureFlags = computeFlagsFromLoadResult(configLoadResult);
354
395
  const isSplitPipeline = isFeatureEnabled(featureFlags, 'diagnostician_split_pipeline');
355
396
  const pipelineTimeoutMs = isSplitPipeline ? SPLIT_PIPELINE_TOTAL_TIMEOUT_MS : 300_000;
397
+ // BUG-1 (PRI-442): extract effectiveConfig so ADR-0019 LLM rate-limit
398
+ // degradation (isDegradationEnabled in base-peer-runner) can read the
399
+ // diagnostician_llm_degradation feature flag. Without this, the runners
400
+ // receive no effectiveConfig and degradation silently never fires.
401
+ // CR-1 (CodeRabbit P2, rc-9): warn when config load failed so the
402
+ // fallback to defaults is observable — no silent degradation.
403
+ if (!configLoadResult.ok) {
404
+ const errSummary = configLoadResult.errors
405
+ .map((e) => `${e.path}: ${e.reason}`)
406
+ .join('; ');
407
+ console.warn(
408
+ `[pd diagnose] .pd/config.yaml at ${configLoadResult.configPath} is malformed — falling back to default feature flags. Errors: ${errSummary}. Next action: ${configLoadResult.errors[0]?.nextAction ?? 'fix config errors and retry'}`,
409
+ );
410
+ }
411
+ const effectiveConfig = configLoadResult.ok ? configLoadResult.effective : configLoadResult.defaults;
356
412
 
357
413
  let runner: DiagnosticianRunnerLike;
358
414
  if (isSplitPipeline) {
359
415
  const resolvedKind = typeof runtimeAdapter.kind === 'function' ? runtimeAdapter.kind() : runtimeKind;
360
416
  const perStageTimeoutMs = pipelineTimeoutMs / 3;
361
417
  const rootCauseRunner = new DiagRootCauseRunner(
362
- { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagRootCauseValidator(), contextAssembler },
363
- { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
418
+ { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagRootCauseValidator(), contextAssembler, contentHashFn },
419
+ { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
364
420
  );
365
421
  const distillerRunner = new DiagDistillerRunner(
366
- { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagDistillerValidator() },
367
- { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
422
+ { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, validator: new DefaultDiagDistillerValidator(), contentHashFn },
423
+ { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
368
424
  );
369
425
  const routerRunner = new DiagRouterRunner(
370
- { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, committer },
371
- { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs },
426
+ { stateManager, runtimeAdapter, eventEmitter, artifactStore: stateManager.piArtifactStore, committer, contentHashFn },
427
+ { owner: 'pd-cli-diagnose', runtimeKind: resolvedKind, outputLanguage, timeoutMs: perStageTimeoutMs, effectiveConfig },
372
428
  );
373
429
 
374
430
  runner = new SplitDiagnosticianRunner({
@@ -463,6 +519,23 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
463
519
  const intakeService = new CandidateIntakeService({ stateManager, ledgerAdapter });
464
520
 
465
521
  for (const candidate of candidates) {
522
+ // PRI-503: admission gate check — refuse non-admitted candidates before
523
+ // intake, mirroring candidate.ts (intake/repair/backfill). Without this,
524
+ // `pd diagnose --intake` could bypass the gate and intake unreviewed
525
+ // candidates. cli-5-failure-no-mutation: refusal path does not call
526
+ // intake(), does not update candidate status, does not write ledger.
527
+ // cli-6-output-next-action: refusal result carries admissionBlock.nextAction.
528
+ const admissionBlock = checkAdmissionGate(candidate);
529
+ if (admissionBlock) {
530
+ intakeFailed = true;
531
+ intakeResults.push({
532
+ candidateId: candidate.candidateId,
533
+ status: 'intake_failed',
534
+ error: `Admission gate refused: ${admissionBlock.reason}`,
535
+ nextAction: admissionBlock.nextAction,
536
+ });
537
+ continue;
538
+ }
466
539
  try {
467
540
  const entry = await intakeService.intake(candidate.candidateId);
468
541
  if (candidate.status !== 'consumed') {
@@ -486,11 +559,79 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
486
559
  }
487
560
  }
488
561
 
562
+ // Defect-004 fix: seed dreamer task for each consumed candidate whose
563
+ // recommendation_kind routes to an MVP-enabled internalization channel.
564
+ // Without this, the chain stops at 'consumed' and never reaches internalization
565
+ // (11 of 18 consumed candidates hit this in production). Mirrors the pattern
566
+ // in PainSignalBridge.onDiagnosisComplete (pain-signal-bridge.ts L310-338).
567
+ //
568
+ // DEFECT-005 (PRI-514): the dreamer seed loop is gated ONLY by `opts.intake`,
569
+ // NOT by a global `intakeFailed` flag. A single defer candidate whose intake
570
+ // is refused by the admission gate must NOT poison dreamer seed for sibling
571
+ // candidates that were successfully consumed (EP-03 / ERR-089 sibling-branch
572
+ // defect). Per-candidate eligibility is checked inside the loop via the
573
+ // intake result's status. `intakeFailed` is retained solely for the
574
+ // nextAction/exit-code signaling below (partial failure still exits non-zero).
575
+ if (opts.intake !== false) {
576
+ for (const candidate of candidates) {
577
+ const kind = candidate.recommendationKind;
578
+ if (kind === 'defer' || kind === 'implementation') continue;
579
+ const intakeResult = intakeResults.find((r) => r.candidateId === candidate.candidateId);
580
+ if (!intakeResult || intakeResult.status !== 'consumed') continue;
581
+ try {
582
+ const route = CANDIDATE_KIND_TO_ROUTE[kind ?? ''];
583
+ if (!route) continue;
584
+ const channel = ROUTE_CHANNEL_MAP[route];
585
+ const ready = !!channel && MVP_ENABLED_CHANNELS.has(channel);
586
+ // BUG-2 (PRI-442): resolve sourcePainId from the diagnostician task
587
+ // chain (rc-6 lineage consistency). The candidate itself doesn't carry
588
+ // sourcePainId; it must be resolved from the diagnostician task's
589
+ // diagnosticJson. ERR-004: never invent lineage.
590
+ const sourcePainId = await resolveSourcePainIdFromDiagnostician(stateManager, candidate);
591
+ const seed = buildDreamerSeedFromCandidate(candidate, { route, ready, sourcePainId: sourcePainId ?? undefined });
592
+ // eslint-disable-next-line no-restricted-syntax -- 'in' required for discriminated union narrowing (BridgeTaskSeed | BridgeDecision)
593
+ if ('decision' in seed) continue; // not_internalizable or invalid — skip
594
+ const existingTask = await stateManager.getTask(seed.taskId);
595
+ if (!existingTask) {
596
+ await stateManager.createTask({
597
+ taskId: seed.taskId,
598
+ taskKind: seed.taskKind,
599
+ inputRef: '',
600
+ status: seed.status,
601
+ attemptCount: seed.attemptCount,
602
+ maxAttempts: seed.maxAttempts,
603
+ diagnosticJson: seed.diagnosticJson,
604
+ });
605
+ intakeResults.push({
606
+ candidateId: candidate.candidateId,
607
+ status: 'dreamer_seeded',
608
+ nextAction: `pd task show ${seed.taskId} | pd dreamer run --task-id ${seed.taskId}`,
609
+ });
610
+ }
611
+ } catch (seedErr) {
612
+ intakeResults.push({
613
+ candidateId: candidate.candidateId,
614
+ status: 'dreamer_seed_failed',
615
+ error: seedErr instanceof Error ? seedErr.message : String(seedErr),
616
+ nextAction: `pd candidate internalize --candidate-id ${candidate.candidateId}`,
617
+ });
618
+ }
619
+ }
620
+ }
621
+
489
622
  if (opts.json) {
490
623
  const candidateIds = candidates.map((c) => c.candidateId);
491
- const internalizeNextAction = candidateIds.length > 0
492
- ? `Candidates generated but internalization has NOT started automatically. To begin internalization, run:\n ${candidateIds.map((id) => `pd candidate internalize --candidate-id ${id} --workspace "${workspaceDir}"`).join('\n ')}`
493
- : 'No candidates were generated from this diagnosis.';
624
+ // CodeRabbit review fix: reflect dreamer seed status in nextAction so
625
+ // the owner knows whether internalization has already started or still
626
+ // needs manual candidate internalize. Previously the message always
627
+ // said "NOT started automatically" even when dreamer tasks were seeded.
628
+ const dreamerSeededCount = intakeResults.filter((r) => r.status === 'dreamer_seeded').length;
629
+ const dreamerSeedFailedCount = intakeResults.filter((r) => r.status === 'dreamer_seed_failed').length;
630
+ const internalizeNextAction = dreamerSeededCount > 0
631
+ ? `Dreamer tasks seeded automatically for ${dreamerSeededCount} candidate(s). To continue, run the dreamer tasks shown in intake.candidates[].${dreamerSeedFailedCount > 0 ? ` (${dreamerSeedFailedCount} candidate(s) failed seeding — see intake.candidates[] for retry guidance.)` : ''}`
632
+ : candidateIds.length > 0
633
+ ? `Candidates generated but internalization has NOT started automatically. To begin internalization, run:\n ${candidateIds.map((id) => `pd candidate internalize --candidate-id ${id} --workspace "${workspaceDir}"`).join('\n ')}`
634
+ : 'No candidates were generated from this diagnosis.';
494
635
  const jsonOutput = {
495
636
  ...result,
496
637
  intake: {
@@ -534,6 +675,13 @@ export async function handleDiagnoseRun(opts: DiagnoseRunOptions): Promise<void>
534
675
  } else if (ir.status === 'intake_failed') {
535
676
  console.log(` ${ir.candidateId}: INTAKE FAILED — ${ir.error}`);
536
677
  console.log(` Next action: pd candidate intake --candidate-id ${ir.candidateId} --workspace "${workspaceDir}"`);
678
+ } else if (ir.status === 'dreamer_seeded') {
679
+ // CodeRabbit review fix: surface dreamer seed success in TTY output
680
+ console.log(` ${ir.candidateId}: dreamer seeded — ${ir.nextAction}`);
681
+ } else if (ir.status === 'dreamer_seed_failed') {
682
+ // CodeRabbit review fix: surface dreamer seed failure in TTY output
683
+ console.log(` ${ir.candidateId}: DREAMER SEED FAILED — ${ir.error}`);
684
+ console.log(` Next action: ${ir.nextAction}`);
537
685
  }
538
686
  }
539
687
  }