@principles/core 1.123.0 → 1.125.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.
- package/dist/runtime-v2/__tests__/architecture-regression.test.js +42 -74
- package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/attack-e2e-pipeline-smoke.test.js +115 -21
- package/dist/runtime-v2/__tests__/attack-e2e-pipeline-smoke.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/golden-path-diagnostician-e2e.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/pain-signal-bridge-retried.test.js +7 -6
- package/dist/runtime-v2/__tests__/pain-signal-bridge-retried.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/pain-signal-bridge-short-circuit.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/pain-signal-bridge-workspace-dir.test.js.map +1 -1
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +4 -0
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
- package/dist/runtime-v2/cli/diagnose.d.ts +2 -2
- package/dist/runtime-v2/cli/diagnose.d.ts.map +1 -1
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +3 -3
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
- package/dist/runtime-v2/config/pd-config-defaults.js +3 -3
- package/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/dist/runtime-v2/config/pd-config-types.d.ts +2 -0
- package/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/dist/runtime-v2/diagnostician/__tests__/diag-distiller-output.test.d.ts +2 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-distiller-output.test.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-distiller-output.test.js +45 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-distiller-output.test.js.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.d.ts +2 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.js +57 -0
- package/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.js.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/distiller-prompt-builder.test.d.ts +2 -0
- package/dist/runtime-v2/diagnostician/__tests__/distiller-prompt-builder.test.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/distiller-prompt-builder.test.js +39 -0
- package/dist/runtime-v2/diagnostician/__tests__/distiller-prompt-builder.test.js.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/rootcause-prompt-builder.test.d.ts +2 -0
- package/dist/runtime-v2/diagnostician/__tests__/rootcause-prompt-builder.test.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/rootcause-prompt-builder.test.js +34 -0
- package/dist/runtime-v2/diagnostician/__tests__/rootcause-prompt-builder.test.js.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/router-prompt-builder.test.d.ts +2 -0
- package/dist/runtime-v2/diagnostician/__tests__/router-prompt-builder.test.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/__tests__/router-prompt-builder.test.js +57 -0
- package/dist/runtime-v2/diagnostician/__tests__/router-prompt-builder.test.js.map +1 -0
- package/dist/runtime-v2/diagnostician/diag-distiller-output.d.ts +80 -0
- package/dist/runtime-v2/diagnostician/diag-distiller-output.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/diag-distiller-output.js +103 -0
- package/dist/runtime-v2/diagnostician/diag-distiller-output.js.map +1 -0
- package/dist/runtime-v2/diagnostician/diag-rootcause-output.d.ts +115 -0
- package/dist/runtime-v2/diagnostician/diag-rootcause-output.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/diag-rootcause-output.js +166 -0
- package/dist/runtime-v2/diagnostician/diag-rootcause-output.js.map +1 -0
- package/dist/runtime-v2/diagnostician/distiller-prompt-builder.d.ts +134 -0
- package/dist/runtime-v2/diagnostician/distiller-prompt-builder.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/distiller-prompt-builder.js +156 -0
- package/dist/runtime-v2/diagnostician/distiller-prompt-builder.js.map +1 -0
- package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts +96 -0
- package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js +216 -0
- package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js.map +1 -0
- package/dist/runtime-v2/diagnostician/router-prompt-builder.d.ts +127 -0
- package/dist/runtime-v2/diagnostician/router-prompt-builder.d.ts.map +1 -0
- package/dist/runtime-v2/diagnostician/router-prompt-builder.js +131 -0
- package/dist/runtime-v2/diagnostician/router-prompt-builder.js.map +1 -0
- package/dist/runtime-v2/diagnostician-prompt-builder.d.ts +1 -64
- package/dist/runtime-v2/diagnostician-prompt-builder.d.ts.map +1 -1
- package/dist/runtime-v2/diagnostician-prompt-builder.js +0 -186
- package/dist/runtime-v2/diagnostician-prompt-builder.js.map +1 -1
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +13 -7
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js +3 -3
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/index.d.ts +14 -10
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +9 -6
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/__fixtures__/split-pipeline-mock-outputs.d.ts +25 -0
- package/dist/runtime-v2/internalization/__tests__/__fixtures__/split-pipeline-mock-outputs.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/__fixtures__/split-pipeline-mock-outputs.js +123 -0
- package/dist/runtime-v2/internalization/__tests__/__fixtures__/split-pipeline-mock-outputs.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js +734 -0
- package/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-distiller-runner.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/diag-distiller-runner.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-distiller-runner.test.js +437 -0
- package/dist/runtime-v2/internalization/__tests__/diag-distiller-runner.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js +336 -0
- package/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-router-runner.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/diag-router-runner.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/diag-router-runner.test.js +437 -0
- package/dist/runtime-v2/internalization/__tests__/diag-router-runner.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/runnerkind-seam.test.js +3 -3
- package/dist/runtime-v2/internalization/__tests__/runnerkind-seam.test.js.map +1 -1
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts +83 -0
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/diag-distiller-runner.js +274 -0
- package/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -0
- package/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts +73 -0
- package/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js +229 -0
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -0
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts +85 -0
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/diag-router-runner.js +353 -0
- package/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -0
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/split-diagnostician-runner.d.ts +66 -0
- package/dist/runtime-v2/internalization/split-diagnostician-runner.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/split-diagnostician-runner.js +241 -0
- package/dist/runtime-v2/internalization/split-diagnostician-runner.js.map +1 -0
- package/dist/runtime-v2/observer/__tests__/empathy-observer.real-e2e.test.js +28 -21
- package/dist/runtime-v2/observer/__tests__/empathy-observer.real-e2e.test.js.map +1 -1
- package/dist/runtime-v2/pain-signal-bridge.d.ts +22 -3
- package/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
- package/dist/runtime-v2/pain-signal-bridge.js +16 -7
- package/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
- package/dist/runtime-v2/pain-signal-runtime-factory.d.ts +13 -1
- package/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/dist/runtime-v2/pain-signal-runtime-factory.js +64 -34
- package/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +2 -2
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/diagnose.test.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +3 -3
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +6 -6
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/peer-runner-types.d.ts +3 -3
- package/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -1
- package/dist/runtime-v2/store/candidate/sqlite-candidate-store.js +2 -2
- package/dist/runtime-v2/store/candidate/sqlite-candidate-store.js.map +1 -1
- package/dist/runtime-v2/types/principle-enums.d.ts +2 -2
- package/dist/runtime-v2/types/principle-enums.d.ts.map +1 -1
- package/dist/runtime-v2/types/principle-enums.js +1 -0
- package/dist/runtime-v2/types/principle-enums.js.map +1 -1
- package/dist/runtime-v2/types/principle-schema.d.ts +1 -1
- package/dist/runtime-v2/types/principle-tree-store.d.ts +1 -1
- package/dist/telemetry-event.d.ts +2 -2
- package/dist/telemetry-event.d.ts.map +1 -1
- package/dist/telemetry-event.js +1 -0
- package/dist/telemetry-event.js.map +1 -1
- package/package.json +1 -1
- package/dist/runtime-v2/__tests__/diagnostician-core-grounding.test.d.ts +0 -2
- package/dist/runtime-v2/__tests__/diagnostician-core-grounding.test.d.ts.map +0 -1
- package/dist/runtime-v2/__tests__/diagnostician-core-grounding.test.js +0 -122
- package/dist/runtime-v2/__tests__/diagnostician-core-grounding.test.js.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.integration.test.d.ts +0 -2
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.integration.test.js +0 -169
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.integration.test.js.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.d.ts +0 -2
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.d.ts.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.js +0 -462
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.js.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-language.test.d.ts +0 -13
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-language.test.d.ts.map +0 -1
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-language.test.js +0 -97
- package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-language.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.integration.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.integration.test.js +0 -378
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.integration.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.test.js +0 -682
- package/dist/runtime-v2/runner/__tests__/diagnostician-runner.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-telemetry.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/diagnostician-telemetry.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/diagnostician-telemetry.test.js +0 -286
- package/dist/runtime-v2/runner/__tests__/diagnostician-telemetry.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/dual-track-e2e.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/dual-track-e2e.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/dual-track-e2e.test.js +0 -320
- package/dist/runtime-v2/runner/__tests__/dual-track-e2e.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/lease-expiration-recovery.integration.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/lease-expiration-recovery.integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/lease-expiration-recovery.integration.test.js +0 -261
- package/dist/runtime-v2/runner/__tests__/lease-expiration-recovery.integration.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m5-05-e2e.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m5-05-e2e.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m5-05-e2e.test.js +0 -405
- package/dist/runtime-v2/runner/__tests__/m5-05-e2e.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-e2e.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m6-06-e2e.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-e2e.test.js +0 -347
- package/dist/runtime-v2/runner/__tests__/m6-06-e2e.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-legacy.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m6-06-legacy.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-legacy.test.js +0 -186
- package/dist/runtime-v2/runner/__tests__/m6-06-legacy.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-real-path.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m6-06-real-path.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m6-06-real-path.test.js +0 -355
- package/dist/runtime-v2/runner/__tests__/m6-06-real-path.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m8-02-e2e.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m8-02-e2e.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m8-02-e2e.test.js +0 -486
- package/dist/runtime-v2/runner/__tests__/m8-02-e2e.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m9-adapter-integration.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m9-adapter-integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m9-adapter-integration.test.js +0 -171
- package/dist/runtime-v2/runner/__tests__/m9-adapter-integration.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m9-e2e.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/m9-e2e.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/m9-e2e.test.js +0 -175
- package/dist/runtime-v2/runner/__tests__/m9-e2e.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/max-attempts-exceeded.integration.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/max-attempts-exceeded.integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/max-attempts-exceeded.integration.test.js +0 -276
- package/dist/runtime-v2/runner/__tests__/max-attempts-exceeded.integration.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/retry-wait-recovery.integration.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/retry-wait-recovery.integration.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/retry-wait-recovery.integration.test.js +0 -272
- package/dist/runtime-v2/runner/__tests__/retry-wait-recovery.integration.test.js.map +0 -1
- package/dist/runtime-v2/runner/__tests__/start-run-input.test.d.ts +0 -2
- package/dist/runtime-v2/runner/__tests__/start-run-input.test.d.ts.map +0 -1
- package/dist/runtime-v2/runner/__tests__/start-run-input.test.js +0 -67
- package/dist/runtime-v2/runner/__tests__/start-run-input.test.js.map +0 -1
- package/dist/runtime-v2/runner/diagnostician-runner-options.d.ts +0 -57
- package/dist/runtime-v2/runner/diagnostician-runner-options.d.ts.map +0 -1
- package/dist/runtime-v2/runner/diagnostician-runner-options.js +0 -21
- package/dist/runtime-v2/runner/diagnostician-runner-options.js.map +0 -1
- package/dist/runtime-v2/runner/diagnostician-runner.d.ts +0 -89
- package/dist/runtime-v2/runner/diagnostician-runner.d.ts.map +0 -1
- package/dist/runtime-v2/runner/diagnostician-runner.js +0 -470
- package/dist/runtime-v2/runner/diagnostician-runner.js.map +0 -1
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diag Chain E2E — Integration test for the split diagnostician pipeline (PRI-372).
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* 1. One pain signal completes A→B→C chain (schema validation + artifact consistency)
|
|
6
|
+
* 2. Flag off: monolith runs unchanged (DiagnosticianRunner used, not split runners)
|
|
7
|
+
* 3. split && !async_cli → fail loud at startup
|
|
8
|
+
* 4. split && async_cli → valid, runners instantiated
|
|
9
|
+
*
|
|
10
|
+
* ERR entries considered:
|
|
11
|
+
* - ERR-001: Treat parsed JSON / LLM output as unknown
|
|
12
|
+
* - ERR-004: Lineage fields must be internally consistent
|
|
13
|
+
* - ERR-008: sourceTaskId/sourceRunIds must match
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
16
|
+
import { DiagRootCauseOutputV1Schema } from '../../diagnostician/diag-rootcause-output.js';
|
|
17
|
+
import { DiagDistillerOutputV1Schema } from '../../diagnostician/diag-distiller-output.js';
|
|
18
|
+
import { DiagnosticianOutputV1Schema } from '../../diagnostician-output.js';
|
|
19
|
+
import { Value } from '@sinclair/typebox/value';
|
|
20
|
+
import { DiagRootCauseRunner } from '../diag-rootcause-runner.js';
|
|
21
|
+
import { DiagDistillerRunner } from '../diag-distiller-runner.js';
|
|
22
|
+
import { DiagRouterRunner } from '../diag-router-runner.js';
|
|
23
|
+
import { SplitDiagnosticianRunner } from '../split-diagnostician-runner.js';
|
|
24
|
+
import { MemoryPIArtifactStore } from '../pi-artifact-store.js';
|
|
25
|
+
import { createPITaskDiagnosticJson } from '../pitask-metadata.js';
|
|
26
|
+
import { computeFeatureFlagsFromConfig, isFeatureEnabled } from '../../config/pd-config-feature-flags.js';
|
|
27
|
+
import { PDRuntimeError } from '../../error-categories.js';
|
|
28
|
+
import { MOCK_ROOT_CAUSE_OUTPUTS, MOCK_DISTILLER_OUTPUTS, MOCK_ROUTER_OUTPUTS } from './__fixtures__/split-pipeline-mock-outputs.js';
|
|
29
|
+
// ── Test fixtures ──────────────────────────────────────────────────────────────
|
|
30
|
+
const ROOTCAUSE_TASK_ID = 'diag_rootcause-e2e';
|
|
31
|
+
const DISTILLER_TASK_ID = 'diag_distiller-e2e';
|
|
32
|
+
const ROUTER_TASK_ID = 'diag_router-e2e';
|
|
33
|
+
const ROOTCAUSE_ARTIFACT_ID = 'pi-art-rootcause-e2e';
|
|
34
|
+
const DISTILLER_ARTIFACT_ID = 'pi-art-distiller-e2e';
|
|
35
|
+
const OWNER = 'test-e2e-owner';
|
|
36
|
+
const RUNTIME_KIND = 'test-double';
|
|
37
|
+
/** Happy-path output using cached real LLM data (R6 fixture) with test-local IDs. */
|
|
38
|
+
function makeRootCauseOutput() {
|
|
39
|
+
return {
|
|
40
|
+
...MOCK_ROOT_CAUSE_OUTPUTS.R6,
|
|
41
|
+
diagnosisId: 'diag-e2e-001',
|
|
42
|
+
taskId: ROOTCAUSE_TASK_ID,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Happy-path output using cached real LLM data (R6 fixture) with test-local IDs. */
|
|
46
|
+
function makeDistillerOutput(overrides = {}) {
|
|
47
|
+
return {
|
|
48
|
+
...MOCK_DISTILLER_OUTPUTS.R6,
|
|
49
|
+
taskId: DISTILLER_TASK_ID,
|
|
50
|
+
sourceRootCauseArtifactId: ROOTCAUSE_ARTIFACT_ID,
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Happy-path output using cached real LLM data (R6 fixture) with test-local IDs. */
|
|
55
|
+
function makeRouterOutput() {
|
|
56
|
+
return {
|
|
57
|
+
...MOCK_ROUTER_OUTPUTS.R6,
|
|
58
|
+
diagnosisId: 'diag-e2e-001',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function makeContextPayload() {
|
|
62
|
+
return {
|
|
63
|
+
sourceRefs: ['ref-1', 'ref-2'],
|
|
64
|
+
conversationWindow: [],
|
|
65
|
+
trajectorySummary: '',
|
|
66
|
+
painSignal: { painId: 'pain-e2e-001', painType: 'tool_failure', source: 'test', reason: 'test reason', score: 70 },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// ── Shared mock helpers ────────────────────────────────────────────────────────
|
|
70
|
+
function makeRunHandle(runId) {
|
|
71
|
+
return { runId, runtimeKind: RUNTIME_KIND, startedAt: new Date().toISOString() };
|
|
72
|
+
}
|
|
73
|
+
function makeSucceededStatus(runId) {
|
|
74
|
+
return { status: 'succeeded', runId };
|
|
75
|
+
}
|
|
76
|
+
function makeMockStateManager(taskOverrides) {
|
|
77
|
+
return {
|
|
78
|
+
acquireLease: vi.fn().mockImplementation((params) => {
|
|
79
|
+
const task = taskOverrides[params.taskId];
|
|
80
|
+
return task ? Promise.resolve(task) : Promise.resolve(undefined);
|
|
81
|
+
}),
|
|
82
|
+
getTask: vi.fn().mockImplementation((id) => {
|
|
83
|
+
return Promise.resolve(taskOverrides[id] ?? undefined);
|
|
84
|
+
}),
|
|
85
|
+
getRunsByTask: vi.fn().mockImplementation((taskId) => {
|
|
86
|
+
// Return a run record for each task so resolveStoreRunId works
|
|
87
|
+
return Promise.resolve([{ runId: `run-${taskId}`, taskId }]);
|
|
88
|
+
}),
|
|
89
|
+
updateRunOutput: vi.fn().mockResolvedValue(undefined),
|
|
90
|
+
markTaskSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
markTaskFailed: vi.fn().mockResolvedValue(undefined),
|
|
92
|
+
markTaskRetryWait: vi.fn().mockResolvedValue(undefined),
|
|
93
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: () => false }),
|
|
94
|
+
createTask: vi.fn().mockImplementation((record) => {
|
|
95
|
+
const task = {
|
|
96
|
+
...record,
|
|
97
|
+
createdAt: new Date().toISOString(),
|
|
98
|
+
updatedAt: new Date().toISOString(),
|
|
99
|
+
};
|
|
100
|
+
taskOverrides[record.taskId] = task;
|
|
101
|
+
return Promise.resolve(task);
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function makeMockRuntimeAdapter() {
|
|
106
|
+
return {
|
|
107
|
+
kind: vi.fn().mockReturnValue(RUNTIME_KIND),
|
|
108
|
+
getCapabilities: vi.fn(),
|
|
109
|
+
healthCheck: vi.fn(),
|
|
110
|
+
startRun: vi.fn().mockResolvedValue(makeRunHandle('run-e2e')),
|
|
111
|
+
pollRun: vi.fn().mockResolvedValue(makeSucceededStatus('run-e2e')),
|
|
112
|
+
fetchOutput: vi.fn().mockResolvedValue({ payload: makeRootCauseOutput() }),
|
|
113
|
+
cancelRun: vi.fn().mockResolvedValue(undefined),
|
|
114
|
+
fetchArtifacts: vi.fn(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function makeMockEventEmitter() {
|
|
118
|
+
return {
|
|
119
|
+
emitTelemetry: vi.fn(),
|
|
120
|
+
on: vi.fn(),
|
|
121
|
+
emit: vi.fn(),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function makeDefaultInternalAgents() {
|
|
125
|
+
return {
|
|
126
|
+
defaultRuntime: 'default',
|
|
127
|
+
agents: {
|
|
128
|
+
diagnostician: { enabled: true },
|
|
129
|
+
dreamer: { enabled: true },
|
|
130
|
+
philosopher: { enabled: true },
|
|
131
|
+
scribe: { enabled: true },
|
|
132
|
+
artificer: { enabled: true },
|
|
133
|
+
evaluator: { enabled: true },
|
|
134
|
+
rolloutReviewer: { enabled: true },
|
|
135
|
+
trainer: { enabled: true },
|
|
136
|
+
correctionObserver: { enabled: true },
|
|
137
|
+
empathyObserver: { enabled: true },
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// ── Tests ──────────────────────────────────────────────────────────────────────
|
|
142
|
+
describe('Diag chain e2e', () => {
|
|
143
|
+
// ── Schema validation tests ────────────────────────────────────────────────
|
|
144
|
+
it('Stage A output passes TypeBox schema validation', () => {
|
|
145
|
+
const output = makeRootCauseOutput();
|
|
146
|
+
expect(Value.Check(DiagRootCauseOutputV1Schema, output)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
it('Stage B output passes TypeBox schema validation and references Stage A artifact', () => {
|
|
149
|
+
const output = makeDistillerOutput();
|
|
150
|
+
expect(Value.Check(DiagDistillerOutputV1Schema, output)).toBe(true);
|
|
151
|
+
// Lineage integrity: sourceRootCauseArtifactId must reference Stage A
|
|
152
|
+
expect(output.sourceRootCauseArtifactId).toBe(ROOTCAUSE_ARTIFACT_ID);
|
|
153
|
+
});
|
|
154
|
+
it('Stage C output passes TypeBox schema validation', () => {
|
|
155
|
+
const output = makeRouterOutput();
|
|
156
|
+
expect(Value.Check(DiagnosticianOutputV1Schema, output)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('artifact chain is internally consistent', () => {
|
|
159
|
+
const rootCauseOutput = makeRootCauseOutput();
|
|
160
|
+
const distillerOutput = makeDistillerOutput();
|
|
161
|
+
const routerOutput = makeRouterOutput();
|
|
162
|
+
// Stage B references Stage A artifact
|
|
163
|
+
expect(distillerOutput.sourceRootCauseArtifactId).toBe(ROOTCAUSE_ARTIFACT_ID);
|
|
164
|
+
// All outputs have valid=true
|
|
165
|
+
expect(rootCauseOutput.valid).toBe(true);
|
|
166
|
+
expect(distillerOutput.valid).toBe(true);
|
|
167
|
+
expect(routerOutput.valid).toBe(true);
|
|
168
|
+
// Root cause category is consistent across stages
|
|
169
|
+
expect(rootCauseOutput.rootCauseCategory).toBe('Design');
|
|
170
|
+
expect(rootCauseOutput.rootCause).toContain('Design:');
|
|
171
|
+
expect(routerOutput.rootCause).toContain('Design:');
|
|
172
|
+
});
|
|
173
|
+
// ── Full A→B→C chain test ──────────────────────────────────────────────────
|
|
174
|
+
it('one pain signal completes A→B→C and triggers committer + onDiagnosisComplete', async () => {
|
|
175
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
176
|
+
const rootCauseTask = {
|
|
177
|
+
taskId: ROOTCAUSE_TASK_ID,
|
|
178
|
+
taskKind: 'diag_rootcause',
|
|
179
|
+
status: 'pending',
|
|
180
|
+
attemptCount: 0,
|
|
181
|
+
maxAttempts: 3,
|
|
182
|
+
createdAt: new Date().toISOString(),
|
|
183
|
+
updatedAt: new Date().toISOString(),
|
|
184
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
185
|
+
dependencyTaskIds: [],
|
|
186
|
+
channel: 'prompt',
|
|
187
|
+
timeoutMs: 300_000,
|
|
188
|
+
inputArtifactRefs: [],
|
|
189
|
+
outputArtifactRefs: [],
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
const distillerTask = {
|
|
193
|
+
taskId: DISTILLER_TASK_ID,
|
|
194
|
+
taskKind: 'diag_distiller',
|
|
195
|
+
status: 'pending',
|
|
196
|
+
attemptCount: 0,
|
|
197
|
+
maxAttempts: 3,
|
|
198
|
+
createdAt: new Date().toISOString(),
|
|
199
|
+
updatedAt: new Date().toISOString(),
|
|
200
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
201
|
+
dependencyTaskIds: [ROOTCAUSE_TASK_ID],
|
|
202
|
+
channel: 'prompt',
|
|
203
|
+
timeoutMs: 300_000,
|
|
204
|
+
inputArtifactRefs: [],
|
|
205
|
+
outputArtifactRefs: [],
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
const routerTask = {
|
|
209
|
+
taskId: ROUTER_TASK_ID,
|
|
210
|
+
taskKind: 'diag_router',
|
|
211
|
+
status: 'pending',
|
|
212
|
+
attemptCount: 0,
|
|
213
|
+
maxAttempts: 3,
|
|
214
|
+
createdAt: new Date().toISOString(),
|
|
215
|
+
updatedAt: new Date().toISOString(),
|
|
216
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
217
|
+
dependencyTaskIds: [ROOTCAUSE_TASK_ID, DISTILLER_TASK_ID],
|
|
218
|
+
channel: 'prompt',
|
|
219
|
+
timeoutMs: 300_000,
|
|
220
|
+
inputArtifactRefs: [],
|
|
221
|
+
outputArtifactRefs: [],
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
224
|
+
const taskMap = {
|
|
225
|
+
[ROOTCAUSE_TASK_ID]: rootCauseTask,
|
|
226
|
+
[DISTILLER_TASK_ID]: distillerTask,
|
|
227
|
+
[ROUTER_TASK_ID]: routerTask,
|
|
228
|
+
};
|
|
229
|
+
const stateManager = makeMockStateManager(taskMap);
|
|
230
|
+
const runtimeAdapter = makeMockRuntimeAdapter();
|
|
231
|
+
const eventEmitter = makeMockEventEmitter();
|
|
232
|
+
const contextAssembler = { assemble: vi.fn().mockResolvedValue(makeContextPayload()) };
|
|
233
|
+
// ── Stage A: DiagRootCauseRunner ──────────────────────────────────────────
|
|
234
|
+
const rootCauseRunId = 'run-rc-e2e';
|
|
235
|
+
runtimeAdapter.startRun.mockResolvedValue(makeRunHandle(rootCauseRunId));
|
|
236
|
+
runtimeAdapter.pollRun.mockResolvedValue(makeSucceededStatus(rootCauseRunId));
|
|
237
|
+
runtimeAdapter.fetchOutput.mockResolvedValue({ payload: makeRootCauseOutput() });
|
|
238
|
+
const rootCauseDeps = {
|
|
239
|
+
stateManager: stateManager,
|
|
240
|
+
runtimeAdapter: runtimeAdapter,
|
|
241
|
+
eventEmitter: eventEmitter,
|
|
242
|
+
artifactStore,
|
|
243
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
244
|
+
contextAssembler: contextAssembler,
|
|
245
|
+
};
|
|
246
|
+
const rootCauseRunner = new DiagRootCauseRunner(rootCauseDeps, {
|
|
247
|
+
owner: OWNER,
|
|
248
|
+
runtimeKind: RUNTIME_KIND,
|
|
249
|
+
pollIntervalMs: 10,
|
|
250
|
+
timeoutMs: 1000,
|
|
251
|
+
});
|
|
252
|
+
const resultA = await rootCauseRunner.run(ROOTCAUSE_TASK_ID);
|
|
253
|
+
expect(resultA.status).toBe('succeeded');
|
|
254
|
+
expect(resultA.artifactId).toBeDefined();
|
|
255
|
+
// Verify Stage A artifact was written
|
|
256
|
+
const artifactsA = await artifactStore.listBySourceTaskId(ROOTCAUSE_TASK_ID);
|
|
257
|
+
expect(artifactsA).toHaveLength(1);
|
|
258
|
+
expect(artifactsA[0]?.artifactKind).toBe('principle');
|
|
259
|
+
// ── Stage B: DiagDistillerRunner ──────────────────────────────────────────
|
|
260
|
+
const distillerRunId = 'run-dist-e2e';
|
|
261
|
+
runtimeAdapter.startRun.mockResolvedValue(makeRunHandle(distillerRunId));
|
|
262
|
+
runtimeAdapter.pollRun.mockResolvedValue(makeSucceededStatus(distillerRunId));
|
|
263
|
+
// Use the actual artifact ID from Stage A for lineage integrity check (EP-07)
|
|
264
|
+
const stageAArtifactId = resultA.artifactId ?? ROOTCAUSE_ARTIFACT_ID;
|
|
265
|
+
runtimeAdapter.fetchOutput.mockResolvedValue({ payload: makeDistillerOutput({ sourceRootCauseArtifactId: stageAArtifactId }) });
|
|
266
|
+
const distillerDeps = {
|
|
267
|
+
stateManager: stateManager,
|
|
268
|
+
runtimeAdapter: runtimeAdapter,
|
|
269
|
+
eventEmitter: eventEmitter,
|
|
270
|
+
artifactStore,
|
|
271
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
272
|
+
};
|
|
273
|
+
const distillerRunner = new DiagDistillerRunner(distillerDeps, {
|
|
274
|
+
owner: OWNER,
|
|
275
|
+
runtimeKind: RUNTIME_KIND,
|
|
276
|
+
pollIntervalMs: 10,
|
|
277
|
+
timeoutMs: 1000,
|
|
278
|
+
});
|
|
279
|
+
const resultB = await distillerRunner.run(DISTILLER_TASK_ID);
|
|
280
|
+
expect(resultB.status).toBe('succeeded');
|
|
281
|
+
expect(resultB.artifactId).toBeDefined();
|
|
282
|
+
// Verify Stage B artifact was written
|
|
283
|
+
const artifactsB = await artifactStore.listBySourceTaskId(DISTILLER_TASK_ID);
|
|
284
|
+
expect(artifactsB).toHaveLength(1);
|
|
285
|
+
// ── Stage C: DiagRouterRunner ─────────────────────────────────────────────
|
|
286
|
+
const routerRunId = 'run-router-e2e';
|
|
287
|
+
runtimeAdapter.startRun.mockResolvedValue(makeRunHandle(routerRunId));
|
|
288
|
+
runtimeAdapter.pollRun.mockResolvedValue(makeSucceededStatus(routerRunId));
|
|
289
|
+
runtimeAdapter.fetchOutput.mockResolvedValue({ payload: makeRouterOutput() });
|
|
290
|
+
const commitResult = {
|
|
291
|
+
commitId: 'commit-e2e-001',
|
|
292
|
+
artifactId: 'art-e2e-001',
|
|
293
|
+
candidateCount: 1,
|
|
294
|
+
};
|
|
295
|
+
const committer = { commit: vi.fn().mockResolvedValue(commitResult) };
|
|
296
|
+
const routerDeps = {
|
|
297
|
+
stateManager: stateManager,
|
|
298
|
+
runtimeAdapter: runtimeAdapter,
|
|
299
|
+
eventEmitter: eventEmitter,
|
|
300
|
+
artifactStore,
|
|
301
|
+
committer: committer,
|
|
302
|
+
};
|
|
303
|
+
const routerRunner = new DiagRouterRunner(routerDeps, {
|
|
304
|
+
owner: OWNER,
|
|
305
|
+
runtimeKind: RUNTIME_KIND,
|
|
306
|
+
pollIntervalMs: 10,
|
|
307
|
+
timeoutMs: 1000,
|
|
308
|
+
});
|
|
309
|
+
const resultC = await routerRunner.run(ROUTER_TASK_ID);
|
|
310
|
+
expect(resultC.status).toBe('succeeded');
|
|
311
|
+
// Verify committer was called
|
|
312
|
+
expect(committer.commit).toHaveBeenCalledWith(expect.objectContaining({
|
|
313
|
+
taskId: ROUTER_TASK_ID,
|
|
314
|
+
}));
|
|
315
|
+
});
|
|
316
|
+
// ── Flag matrix guard tests ────────────────────────────────────────────────
|
|
317
|
+
it('flag off: splitPipeline flag can be disabled via config', () => {
|
|
318
|
+
const effectiveConfig = {
|
|
319
|
+
config: {
|
|
320
|
+
version: 1,
|
|
321
|
+
features: {
|
|
322
|
+
diagnostician_split_pipeline: { category: 'quiet', enabled: false },
|
|
323
|
+
},
|
|
324
|
+
runtimeProfiles: {},
|
|
325
|
+
internalAgents: makeDefaultInternalAgents(),
|
|
326
|
+
ui: { diagnostics: { mode: 'simple' } },
|
|
327
|
+
},
|
|
328
|
+
source: 'user_config',
|
|
329
|
+
warnings: [],
|
|
330
|
+
};
|
|
331
|
+
const featureFlags = computeFeatureFlagsFromConfig(effectiveConfig);
|
|
332
|
+
const splitPipeline = isFeatureEnabled(featureFlags, 'diagnostician_split_pipeline');
|
|
333
|
+
expect(splitPipeline).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
it('split && !async_cli → fail loud at startup', () => {
|
|
336
|
+
// When split_pipeline=true and async_cli=false, the factory guard should throw.
|
|
337
|
+
const effectiveConfig = {
|
|
338
|
+
config: {
|
|
339
|
+
version: 1,
|
|
340
|
+
features: {
|
|
341
|
+
diagnostician_split_pipeline: { category: 'quiet', enabled: true },
|
|
342
|
+
diagnostician_async_cli: { category: 'quiet', enabled: false },
|
|
343
|
+
},
|
|
344
|
+
runtimeProfiles: {},
|
|
345
|
+
internalAgents: makeDefaultInternalAgents(),
|
|
346
|
+
ui: { diagnostics: { mode: 'simple' } },
|
|
347
|
+
},
|
|
348
|
+
source: 'user_config',
|
|
349
|
+
warnings: [],
|
|
350
|
+
};
|
|
351
|
+
const featureFlags = computeFeatureFlagsFromConfig(effectiveConfig);
|
|
352
|
+
const splitPipeline = isFeatureEnabled(featureFlags, 'diagnostician_split_pipeline');
|
|
353
|
+
const asyncCli = isFeatureEnabled(featureFlags, 'diagnostician_async_cli');
|
|
354
|
+
// Simulate the factory guard logic
|
|
355
|
+
if (splitPipeline && !asyncCli) {
|
|
356
|
+
// This is the expected path — the factory would throw
|
|
357
|
+
expect(() => {
|
|
358
|
+
throw new PDRuntimeError('input_invalid', 'diagnostician_split_pipeline requires diagnostician_async_cli=on (3 serial LLM calls would block the sync CLI 540s+)');
|
|
359
|
+
}).toThrow(PDRuntimeError);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Should not reach here
|
|
363
|
+
expect.unreachable('split_pipeline should be true and async_cli should be false');
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
it('split && async_cli → valid, runners instantiated', () => {
|
|
367
|
+
// When both flags are on, the factory should create the 3 split runners.
|
|
368
|
+
const effectiveConfig = {
|
|
369
|
+
config: {
|
|
370
|
+
version: 1,
|
|
371
|
+
features: {
|
|
372
|
+
diagnostician_split_pipeline: { category: 'quiet', enabled: true },
|
|
373
|
+
diagnostician_async_cli: { category: 'quiet', enabled: true },
|
|
374
|
+
},
|
|
375
|
+
runtimeProfiles: {},
|
|
376
|
+
internalAgents: makeDefaultInternalAgents(),
|
|
377
|
+
ui: { diagnostics: { mode: 'simple' } },
|
|
378
|
+
},
|
|
379
|
+
source: 'user_config',
|
|
380
|
+
warnings: [],
|
|
381
|
+
};
|
|
382
|
+
const featureFlags = computeFeatureFlagsFromConfig(effectiveConfig);
|
|
383
|
+
const splitPipeline = isFeatureEnabled(featureFlags, 'diagnostician_split_pipeline');
|
|
384
|
+
const asyncCli = isFeatureEnabled(featureFlags, 'diagnostician_async_cli');
|
|
385
|
+
// Both flags should be enabled
|
|
386
|
+
expect(splitPipeline).toBe(true);
|
|
387
|
+
expect(asyncCli).toBe(true);
|
|
388
|
+
// The guard should NOT throw — verify by simulating the factory guard
|
|
389
|
+
if (splitPipeline && !asyncCli) {
|
|
390
|
+
expect.unreachable('Should not throw when both flags are on');
|
|
391
|
+
}
|
|
392
|
+
// If we reach here, the guard passes — runners would be instantiated
|
|
393
|
+
expect(splitPipeline && asyncCli).toBe(true);
|
|
394
|
+
});
|
|
395
|
+
// ── Cross-stage lineage integrity ──────────────────────────────────────────
|
|
396
|
+
it('Stage B sourceRootCauseArtifactId matches Stage A artifact ID', async () => {
|
|
397
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
398
|
+
// Write Stage A artifact
|
|
399
|
+
await artifactStore.upsertArtifact({
|
|
400
|
+
artifactId: ROOTCAUSE_ARTIFACT_ID,
|
|
401
|
+
artifactKind: 'principle',
|
|
402
|
+
sourceTaskId: ROOTCAUSE_TASK_ID,
|
|
403
|
+
lineageArtifactIds: [],
|
|
404
|
+
validationStatus: 'pending',
|
|
405
|
+
contentJson: JSON.stringify(makeRootCauseOutput()),
|
|
406
|
+
createdAt: new Date().toISOString(),
|
|
407
|
+
updatedAt: new Date().toISOString(),
|
|
408
|
+
});
|
|
409
|
+
// Verify Stage B output references the correct artifact
|
|
410
|
+
const distillerOutput = makeDistillerOutput();
|
|
411
|
+
expect(distillerOutput.sourceRootCauseArtifactId).toBe(ROOTCAUSE_ARTIFACT_ID);
|
|
412
|
+
// Verify the artifact exists in the store
|
|
413
|
+
const artifacts = await artifactStore.listBySourceTaskId(ROOTCAUSE_TASK_ID);
|
|
414
|
+
expect(artifacts).toHaveLength(1);
|
|
415
|
+
expect(artifacts[0]?.artifactId).toBe(ROOTCAUSE_ARTIFACT_ID);
|
|
416
|
+
});
|
|
417
|
+
it('Stage C reads both Stage A and Stage B artifacts', async () => {
|
|
418
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
419
|
+
// Write Stage A artifact
|
|
420
|
+
await artifactStore.upsertArtifact({
|
|
421
|
+
artifactId: ROOTCAUSE_ARTIFACT_ID,
|
|
422
|
+
artifactKind: 'principle',
|
|
423
|
+
sourceTaskId: ROOTCAUSE_TASK_ID,
|
|
424
|
+
lineageArtifactIds: [],
|
|
425
|
+
validationStatus: 'pending',
|
|
426
|
+
contentJson: JSON.stringify(makeRootCauseOutput()),
|
|
427
|
+
createdAt: new Date().toISOString(),
|
|
428
|
+
updatedAt: new Date().toISOString(),
|
|
429
|
+
});
|
|
430
|
+
// Write Stage B artifact
|
|
431
|
+
await artifactStore.upsertArtifact({
|
|
432
|
+
artifactId: DISTILLER_ARTIFACT_ID,
|
|
433
|
+
artifactKind: 'principle',
|
|
434
|
+
sourceTaskId: DISTILLER_TASK_ID,
|
|
435
|
+
lineageArtifactIds: [ROOTCAUSE_ARTIFACT_ID],
|
|
436
|
+
validationStatus: 'pending',
|
|
437
|
+
contentJson: JSON.stringify(makeDistillerOutput()),
|
|
438
|
+
createdAt: new Date().toISOString(),
|
|
439
|
+
updatedAt: new Date().toISOString(),
|
|
440
|
+
});
|
|
441
|
+
// Verify both artifacts exist
|
|
442
|
+
const artifactsA = await artifactStore.listBySourceTaskId(ROOTCAUSE_TASK_ID);
|
|
443
|
+
const artifactsB = await artifactStore.listBySourceTaskId(DISTILLER_TASK_ID);
|
|
444
|
+
expect(artifactsA).toHaveLength(1);
|
|
445
|
+
expect(artifactsB).toHaveLength(1);
|
|
446
|
+
// Verify Stage B lineage includes Stage A artifact
|
|
447
|
+
expect(artifactsB[0]?.lineageArtifactIds).toContain(ROOTCAUSE_ARTIFACT_ID);
|
|
448
|
+
});
|
|
449
|
+
// ── SplitDiagnosticianRunner orchestration test ────────────────────────────
|
|
450
|
+
it('SplitDiagnosticianRunner orchestrates A→B→C and returns RunnerResult', async () => {
|
|
451
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
452
|
+
const PARENT_TASK_ID = 'diagnosis_split-e2e';
|
|
453
|
+
const STAGE_A_TASK_ID = `diag_rootcause-${PARENT_TASK_ID}`;
|
|
454
|
+
const STAGE_B_TASK_ID = `diag_distiller-${PARENT_TASK_ID}`;
|
|
455
|
+
const STAGE_C_TASK_ID = `diag_router-${PARENT_TASK_ID}`;
|
|
456
|
+
const taskMap = {};
|
|
457
|
+
const stateManager = makeMockStateManager(taskMap);
|
|
458
|
+
const runtimeAdapter = makeMockRuntimeAdapter();
|
|
459
|
+
const eventEmitter = makeMockEventEmitter();
|
|
460
|
+
const contextAssembler = { assemble: vi.fn().mockResolvedValue(makeContextPayload()) };
|
|
461
|
+
// Stage A runner setup
|
|
462
|
+
const rootCauseRunId = 'run-split-rc';
|
|
463
|
+
runtimeAdapter.startRun.mockResolvedValue(makeRunHandle(rootCauseRunId));
|
|
464
|
+
runtimeAdapter.pollRun.mockResolvedValue(makeSucceededStatus(rootCauseRunId));
|
|
465
|
+
// The store's run ID is 'run-${taskId}' per makeMockStateManager
|
|
466
|
+
const storeRunIdA = `run-${STAGE_A_TASK_ID}`;
|
|
467
|
+
const expectedStageAArtifactId = `pi-art-${STAGE_A_TASK_ID}-${storeRunIdA}`;
|
|
468
|
+
// fetchOutput returns different outputs per stage — use mockImplementation
|
|
469
|
+
// Stage A is first, Stage B second, Stage C third
|
|
470
|
+
let fetchCallCount = 0;
|
|
471
|
+
runtimeAdapter.fetchOutput.mockImplementation(async () => {
|
|
472
|
+
fetchCallCount++;
|
|
473
|
+
if (fetchCallCount === 1) {
|
|
474
|
+
return { payload: makeRootCauseOutput() };
|
|
475
|
+
}
|
|
476
|
+
if (fetchCallCount === 2) {
|
|
477
|
+
// Stage B: use the artifact ID that Stage A wrote (based on store run ID)
|
|
478
|
+
return { payload: makeDistillerOutput({ sourceRootCauseArtifactId: expectedStageAArtifactId }) };
|
|
479
|
+
}
|
|
480
|
+
return { payload: makeRouterOutput() };
|
|
481
|
+
});
|
|
482
|
+
const rootCauseDeps = {
|
|
483
|
+
stateManager: stateManager,
|
|
484
|
+
runtimeAdapter: runtimeAdapter,
|
|
485
|
+
eventEmitter: eventEmitter,
|
|
486
|
+
artifactStore,
|
|
487
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
488
|
+
contextAssembler: contextAssembler,
|
|
489
|
+
};
|
|
490
|
+
const rootCauseRunner = new DiagRootCauseRunner(rootCauseDeps, {
|
|
491
|
+
owner: OWNER,
|
|
492
|
+
runtimeKind: RUNTIME_KIND,
|
|
493
|
+
pollIntervalMs: 10,
|
|
494
|
+
timeoutMs: 1000,
|
|
495
|
+
});
|
|
496
|
+
// Stage B runner
|
|
497
|
+
const distillerDeps = {
|
|
498
|
+
stateManager: stateManager,
|
|
499
|
+
runtimeAdapter: runtimeAdapter,
|
|
500
|
+
eventEmitter: eventEmitter,
|
|
501
|
+
artifactStore,
|
|
502
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
503
|
+
};
|
|
504
|
+
const distillerRunner = new DiagDistillerRunner(distillerDeps, {
|
|
505
|
+
owner: OWNER,
|
|
506
|
+
runtimeKind: RUNTIME_KIND,
|
|
507
|
+
pollIntervalMs: 10,
|
|
508
|
+
timeoutMs: 1000,
|
|
509
|
+
});
|
|
510
|
+
// Stage C runner
|
|
511
|
+
const commitResult = {
|
|
512
|
+
commitId: 'commit-split-e2e',
|
|
513
|
+
artifactId: 'art-split-e2e',
|
|
514
|
+
candidateCount: 1,
|
|
515
|
+
};
|
|
516
|
+
const committer = { commit: vi.fn().mockResolvedValue(commitResult) };
|
|
517
|
+
const routerDeps = {
|
|
518
|
+
stateManager: stateManager,
|
|
519
|
+
runtimeAdapter: runtimeAdapter,
|
|
520
|
+
eventEmitter: eventEmitter,
|
|
521
|
+
artifactStore,
|
|
522
|
+
committer: committer,
|
|
523
|
+
};
|
|
524
|
+
const routerRunner = new DiagRouterRunner(routerDeps, {
|
|
525
|
+
owner: OWNER,
|
|
526
|
+
runtimeKind: RUNTIME_KIND,
|
|
527
|
+
pollIntervalMs: 10,
|
|
528
|
+
timeoutMs: 1000,
|
|
529
|
+
});
|
|
530
|
+
// Create the split runner
|
|
531
|
+
const splitRunner = new SplitDiagnosticianRunner({
|
|
532
|
+
rootCauseRunner,
|
|
533
|
+
distillerRunner,
|
|
534
|
+
routerRunner,
|
|
535
|
+
stateManager: stateManager,
|
|
536
|
+
committer: committer,
|
|
537
|
+
});
|
|
538
|
+
// Run the split pipeline
|
|
539
|
+
const result = await splitRunner.run(PARENT_TASK_ID);
|
|
540
|
+
// Verify the result
|
|
541
|
+
expect(result.status).toBe('succeeded');
|
|
542
|
+
expect(result.taskId).toBe(PARENT_TASK_ID);
|
|
543
|
+
expect(result.output).toBeDefined();
|
|
544
|
+
expect(result.output?.valid).toBe(true);
|
|
545
|
+
// Verify all 3 sub-tasks were created
|
|
546
|
+
expect(taskMap[STAGE_A_TASK_ID]).toBeDefined();
|
|
547
|
+
expect(taskMap[STAGE_A_TASK_ID]?.taskKind).toBe('diag_rootcause');
|
|
548
|
+
expect(taskMap[STAGE_B_TASK_ID]).toBeDefined();
|
|
549
|
+
expect(taskMap[STAGE_B_TASK_ID]?.taskKind).toBe('diag_distiller');
|
|
550
|
+
expect(taskMap[STAGE_C_TASK_ID]).toBeDefined();
|
|
551
|
+
expect(taskMap[STAGE_C_TASK_ID]?.taskKind).toBe('diag_router');
|
|
552
|
+
// Verify Stage B depends on Stage A
|
|
553
|
+
const stageBDiag = taskMap[STAGE_B_TASK_ID]?.diagnosticJson;
|
|
554
|
+
expect(stageBDiag).toContain(STAGE_A_TASK_ID);
|
|
555
|
+
// Verify Stage C depends on both A and B
|
|
556
|
+
const stageCDiag = taskMap[STAGE_C_TASK_ID]?.diagnosticJson;
|
|
557
|
+
expect(stageCDiag).toContain(STAGE_A_TASK_ID);
|
|
558
|
+
expect(stageCDiag).toContain(STAGE_B_TASK_ID);
|
|
559
|
+
// Verify committer was called (by Stage C)
|
|
560
|
+
expect(committer.commit).toHaveBeenCalled();
|
|
561
|
+
// Verify artifacts were written for all 3 stages
|
|
562
|
+
const artifactsA = await artifactStore.listBySourceTaskId(STAGE_A_TASK_ID);
|
|
563
|
+
const artifactsB = await artifactStore.listBySourceTaskId(STAGE_B_TASK_ID);
|
|
564
|
+
expect(artifactsA).toHaveLength(1);
|
|
565
|
+
expect(artifactsB).toHaveLength(1);
|
|
566
|
+
});
|
|
567
|
+
it('SplitDiagnosticianRunner stops and returns failure when Stage A fails', async () => {
|
|
568
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
569
|
+
const PARENT_TASK_ID = 'diagnosis_split-fail-a';
|
|
570
|
+
const taskMap = {};
|
|
571
|
+
const stateManager = makeMockStateManager(taskMap);
|
|
572
|
+
const runtimeAdapter = makeMockRuntimeAdapter();
|
|
573
|
+
const eventEmitter = makeMockEventEmitter();
|
|
574
|
+
const contextAssembler = { assemble: vi.fn().mockResolvedValue(makeContextPayload()) };
|
|
575
|
+
// Stage A fails
|
|
576
|
+
runtimeAdapter.startRun.mockResolvedValue(makeRunHandle('run-fail-rc'));
|
|
577
|
+
runtimeAdapter.pollRun.mockResolvedValue({ status: 'failed', runId: 'run-fail-rc' });
|
|
578
|
+
const rootCauseDeps = {
|
|
579
|
+
stateManager: stateManager,
|
|
580
|
+
runtimeAdapter: runtimeAdapter,
|
|
581
|
+
eventEmitter: eventEmitter,
|
|
582
|
+
artifactStore,
|
|
583
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
584
|
+
contextAssembler: contextAssembler,
|
|
585
|
+
};
|
|
586
|
+
const rootCauseRunner = new DiagRootCauseRunner(rootCauseDeps, {
|
|
587
|
+
owner: OWNER,
|
|
588
|
+
runtimeKind: RUNTIME_KIND,
|
|
589
|
+
pollIntervalMs: 10,
|
|
590
|
+
timeoutMs: 1000,
|
|
591
|
+
});
|
|
592
|
+
const distillerRunner = new DiagDistillerRunner({ stateManager: stateManager, runtimeAdapter: runtimeAdapter, eventEmitter: eventEmitter, artifactStore, validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) } }, { owner: OWNER, runtimeKind: RUNTIME_KIND, pollIntervalMs: 10, timeoutMs: 1000 });
|
|
593
|
+
const committer = { commit: vi.fn() };
|
|
594
|
+
const routerRunner = new DiagRouterRunner({ stateManager: stateManager, runtimeAdapter: runtimeAdapter, eventEmitter: eventEmitter, artifactStore, committer: committer }, { owner: OWNER, runtimeKind: RUNTIME_KIND, pollIntervalMs: 10, timeoutMs: 1000 });
|
|
595
|
+
const splitRunner = new SplitDiagnosticianRunner({
|
|
596
|
+
rootCauseRunner,
|
|
597
|
+
distillerRunner,
|
|
598
|
+
routerRunner,
|
|
599
|
+
stateManager: stateManager,
|
|
600
|
+
committer: committer,
|
|
601
|
+
});
|
|
602
|
+
const result = await splitRunner.run(PARENT_TASK_ID);
|
|
603
|
+
// Should fail — Stage A failed
|
|
604
|
+
expect(result.status).toBe('failed');
|
|
605
|
+
expect(result.taskId).toBe(PARENT_TASK_ID);
|
|
606
|
+
// The failure reason comes from the runner (max_attempts_exceeded)
|
|
607
|
+
expect(result.errorCategory).toBe('max_attempts_exceeded');
|
|
608
|
+
// Stage B and C tasks should NOT have been created
|
|
609
|
+
expect(taskMap[`diag_distiller-${PARENT_TASK_ID}`]).toBeUndefined();
|
|
610
|
+
expect(taskMap[`diag_router-${PARENT_TASK_ID}`]).toBeUndefined();
|
|
611
|
+
// Committer should NOT have been called
|
|
612
|
+
expect(committer.commit).not.toHaveBeenCalled();
|
|
613
|
+
});
|
|
614
|
+
it('split pipeline end-to-end boundary integration (real SQLite + Bridge + Committer + Intake + Ledger)', async () => {
|
|
615
|
+
const fs = await import('node:fs');
|
|
616
|
+
const path = await import('node:path');
|
|
617
|
+
const os = await import('node:os');
|
|
618
|
+
const { RuntimeStateManager } = await import('../../store/runtime-state-manager.js');
|
|
619
|
+
const { SqliteDiagnosticianCommitter } = await import('../../store/commit/diagnostician-committer.js');
|
|
620
|
+
const { SqliteContextAssembler } = await import('../../store/context/sqlite-context-assembler.js');
|
|
621
|
+
const { SqliteHistoryQuery } = await import('../../store/history/sqlite-history-query.js');
|
|
622
|
+
const { PainSignalBridge } = await import('../../pain-signal-bridge.js');
|
|
623
|
+
const { CandidateIntakeService } = await import('../../candidate-intake-service.js');
|
|
624
|
+
const { DiagRootCauseRunner: DiagRootCauseRunnerImpl } = await import('../diag-rootcause-runner.js');
|
|
625
|
+
const { DiagDistillerRunner: DiagDistillerRunnerImpl } = await import('../diag-distiller-runner.js');
|
|
626
|
+
const { DiagRouterRunner: DiagRouterRunnerImpl } = await import('../diag-router-runner.js');
|
|
627
|
+
const { SplitDiagnosticianRunner: SplitDiagnosticianRunnerImpl } = await import('../split-diagnostician-runner.js');
|
|
628
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-split-e2e-boundary-'));
|
|
629
|
+
const stateManager = new RuntimeStateManager({ workspaceDir: tmpDir });
|
|
630
|
+
try {
|
|
631
|
+
await stateManager.initialize();
|
|
632
|
+
const committer = new SqliteDiagnosticianCommitter(stateManager.connection);
|
|
633
|
+
const trajectoryTurnReader = {
|
|
634
|
+
listUserTurnsForSession: vi.fn().mockReturnValue([]),
|
|
635
|
+
listAssistantTurns: vi.fn().mockReturnValue([]),
|
|
636
|
+
};
|
|
637
|
+
const contextAssembler = new SqliteContextAssembler(stateManager.taskStore, new SqliteHistoryQuery(stateManager.connection), stateManager.runStore, { trajectoryTurnReader });
|
|
638
|
+
const runtimeAdapter = makeMockRuntimeAdapter();
|
|
639
|
+
let runCount = 0;
|
|
640
|
+
runtimeAdapter.startRun.mockImplementation(async () => {
|
|
641
|
+
runCount++;
|
|
642
|
+
return { runId: `run-e2e-${runCount}`, runtimeKind: RUNTIME_KIND, startedAt: new Date().toISOString() };
|
|
643
|
+
});
|
|
644
|
+
runtimeAdapter.pollRun.mockImplementation(async (handle) => {
|
|
645
|
+
return { status: 'succeeded', runId: handle.runId };
|
|
646
|
+
});
|
|
647
|
+
let fetchCallCount = 0;
|
|
648
|
+
runtimeAdapter.fetchOutput.mockImplementation(async () => {
|
|
649
|
+
fetchCallCount++;
|
|
650
|
+
if (fetchCallCount === 1) {
|
|
651
|
+
return { payload: makeRootCauseOutput() };
|
|
652
|
+
}
|
|
653
|
+
if (fetchCallCount === 2) {
|
|
654
|
+
const artifactsA = await stateManager.piArtifactStore.listBySourceTaskId(`diag_rootcause-diagnosis_pain-e2e-boundary`);
|
|
655
|
+
const stageAArtifactId = artifactsA[0]?.artifactId ?? ROOTCAUSE_ARTIFACT_ID;
|
|
656
|
+
return { payload: makeDistillerOutput({ sourceRootCauseArtifactId: stageAArtifactId }) };
|
|
657
|
+
}
|
|
658
|
+
return { payload: makeRouterOutput() };
|
|
659
|
+
});
|
|
660
|
+
const rootCauseRunner = new DiagRootCauseRunnerImpl({
|
|
661
|
+
stateManager,
|
|
662
|
+
runtimeAdapter: runtimeAdapter,
|
|
663
|
+
eventEmitter: makeMockEventEmitter(),
|
|
664
|
+
artifactStore: stateManager.piArtifactStore,
|
|
665
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
666
|
+
contextAssembler,
|
|
667
|
+
}, { owner: OWNER, runtimeKind: RUNTIME_KIND, pollIntervalMs: 10, timeoutMs: 1000 });
|
|
668
|
+
const distillerRunner = new DiagDistillerRunnerImpl({
|
|
669
|
+
stateManager,
|
|
670
|
+
runtimeAdapter: runtimeAdapter,
|
|
671
|
+
eventEmitter: makeMockEventEmitter(),
|
|
672
|
+
artifactStore: stateManager.piArtifactStore,
|
|
673
|
+
validator: { validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }) },
|
|
674
|
+
}, { owner: OWNER, runtimeKind: RUNTIME_KIND, pollIntervalMs: 10, timeoutMs: 1000 });
|
|
675
|
+
const routerRunner = new DiagRouterRunnerImpl({
|
|
676
|
+
stateManager,
|
|
677
|
+
runtimeAdapter: runtimeAdapter,
|
|
678
|
+
eventEmitter: makeMockEventEmitter(),
|
|
679
|
+
artifactStore: stateManager.piArtifactStore,
|
|
680
|
+
committer,
|
|
681
|
+
}, { owner: OWNER, runtimeKind: RUNTIME_KIND, pollIntervalMs: 10, timeoutMs: 1000 });
|
|
682
|
+
const splitRunner = new SplitDiagnosticianRunnerImpl({
|
|
683
|
+
rootCauseRunner,
|
|
684
|
+
distillerRunner,
|
|
685
|
+
routerRunner,
|
|
686
|
+
stateManager,
|
|
687
|
+
committer,
|
|
688
|
+
});
|
|
689
|
+
const ledgerAdapter = {
|
|
690
|
+
writeProbationEntry: vi.fn().mockImplementation((entry) => entry),
|
|
691
|
+
existsForCandidate: vi.fn().mockReturnValue(null),
|
|
692
|
+
};
|
|
693
|
+
const intakeService = new CandidateIntakeService({
|
|
694
|
+
stateManager,
|
|
695
|
+
ledgerAdapter: ledgerAdapter,
|
|
696
|
+
});
|
|
697
|
+
const bridge = new PainSignalBridge({
|
|
698
|
+
stateManager,
|
|
699
|
+
runner: splitRunner,
|
|
700
|
+
intakeService,
|
|
701
|
+
ledgerAdapter: ledgerAdapter,
|
|
702
|
+
autoIntakeEnabled: true,
|
|
703
|
+
workspaceDir: tmpDir,
|
|
704
|
+
});
|
|
705
|
+
const painSignal = {
|
|
706
|
+
painId: 'pain-e2e-boundary',
|
|
707
|
+
painType: 'tool_failure',
|
|
708
|
+
source: 'test-source',
|
|
709
|
+
reason: 'test reason',
|
|
710
|
+
evidence: [{ sourceRef: 'src-1', note: 'some evidence note' }],
|
|
711
|
+
};
|
|
712
|
+
const bridgeResult = await bridge.onPainDetected(painSignal);
|
|
713
|
+
// Verify successful e2e execution status
|
|
714
|
+
expect(bridgeResult.status).toBe('succeeded');
|
|
715
|
+
expect(bridgeResult.painId).toBe(painSignal.painId);
|
|
716
|
+
expect(bridgeResult.taskId).toBe(`diagnosis_${painSignal.painId}`);
|
|
717
|
+
// Verify all 3 sub-tasks were written to the state.db
|
|
718
|
+
const parentTaskId = `diagnosis_${painSignal.painId}`;
|
|
719
|
+
const taskA = await stateManager.getTask(`diag_rootcause-${parentTaskId}`);
|
|
720
|
+
const taskB = await stateManager.getTask(`diag_distiller-${parentTaskId}`);
|
|
721
|
+
const taskC = await stateManager.getTask(`diag_router-${parentTaskId}`);
|
|
722
|
+
expect(taskA?.status).toBe('succeeded');
|
|
723
|
+
expect(taskB?.status).toBe('succeeded');
|
|
724
|
+
expect(taskC?.status).toBe('succeeded');
|
|
725
|
+
// Verify candidate was committed and then registered to the ledger!
|
|
726
|
+
expect(ledgerAdapter.writeProbationEntry).toHaveBeenCalled();
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
stateManager.close();
|
|
730
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
//# sourceMappingURL=diag-chain-e2e.test.js.map
|