@principles/pd-cli 1.152.12 → 1.152.13
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/package.json +13 -7
- package/CHANGELOG.md +0 -23
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
- package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
- package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
- package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
- package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
- package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
- package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
- package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
- package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
- package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
- package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
- package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
- package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
- package/dist/commands/__tests__/version.test.d.ts +0 -2
- package/dist/commands/__tests__/version.test.d.ts.map +0 -1
- package/dist/commands/__tests__/version.test.js +0 -238
- package/dist/commands/__tests__/version.test.js.map +0 -1
- package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
- package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
- package/dist/commands/runtime-uat.guard.test.js +0 -153
- package/dist/commands/runtime-uat.guard.test.js.map +0 -1
- package/dist/services/__tests__/cli-output.test.d.ts +0 -18
- package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
- package/dist/services/__tests__/cli-output.test.js +0 -103
- package/dist/services/__tests__/cli-output.test.js.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
- package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
- package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
- package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
- package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
- package/dist/utils/production-workspace-guard.test.d.ts +0 -2
- package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
- package/dist/utils/production-workspace-guard.test.js +0 -137
- package/dist/utils/production-workspace-guard.test.js.map +0 -1
- package/scripts/llm-dogfood.ts +0 -456
- package/scripts/migrate-illegal-expected-decision.ts +0 -252
- package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
- package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
- package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
- package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
- package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
- package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
- package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
- package/src/commands/__tests__/version.test.ts +0 -251
- package/src/commands/admission-gate.ts +0 -30
- package/src/commands/artifact.ts +0 -82
- package/src/commands/build-trajectory-evidence.ts +0 -298
- package/src/commands/candidate.ts +0 -1250
- package/src/commands/codex-ingest-catchup.ts +0 -114
- package/src/commands/codex-ingest-quarantine.ts +0 -127
- package/src/commands/codex-reconcile.ts +0 -84
- package/src/commands/codex-setup.ts +0 -476
- package/src/commands/codex-worker.ts +0 -185
- package/src/commands/command-helpers.ts +0 -24
- package/src/commands/config-doctor.ts +0 -193
- package/src/commands/console.ts +0 -797
- package/src/commands/context.ts +0 -72
- package/src/commands/demo-story-a.ts +0 -241
- package/src/commands/diagnose.ts +0 -756
- package/src/commands/errors-list.ts +0 -441
- package/src/commands/evolution-tasks-list.ts +0 -48
- package/src/commands/evolution-tasks-show.ts +0 -62
- package/src/commands/flow.ts +0 -60
- package/src/commands/health-codex.ts +0 -576
- package/src/commands/health.ts +0 -315
- package/src/commands/history.ts +0 -63
- package/src/commands/intent.ts +0 -457
- package/src/commands/legacy-cleanup.ts +0 -531
- package/src/commands/legacy-import.ts +0 -104
- package/src/commands/mvp-smoke.ts +0 -160
- package/src/commands/pain-evidence.ts +0 -226
- package/src/commands/pain-list.ts +0 -239
- package/src/commands/pain-record.ts +0 -612
- package/src/commands/pain-retry.ts +0 -916
- package/src/commands/principles-stats.ts +0 -700
- package/src/commands/proven-channel-baseline.ts +0 -115
- package/src/commands/quality-scorecard.ts +0 -272
- package/src/commands/remediation-output.ts +0 -66
- package/src/commands/rulecode.ts +0 -465
- package/src/commands/run.ts +0 -89
- package/src/commands/runtime-activation.ts +0 -1629
- package/src/commands/runtime-artifact-repair.ts +0 -524
- package/src/commands/runtime-canary.ts +0 -395
- package/src/commands/runtime-compatibility-scan.ts +0 -105
- package/src/commands/runtime-diagnostics-export.ts +0 -229
- package/src/commands/runtime-features.ts +0 -153
- package/src/commands/runtime-gfi-snapshot.ts +0 -135
- package/src/commands/runtime-health-snapshot.ts +0 -108
- package/src/commands/runtime-init.ts +0 -429
- package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
- package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
- package/src/commands/runtime-internalization-integrity.ts +0 -107
- package/src/commands/runtime-internalization-queue.ts +0 -135
- package/src/commands/runtime-internalization-retry.ts +0 -191
- package/src/commands/runtime-internalization-run-once.ts +0 -791
- package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
- package/src/commands/runtime-internalization-wake-once.ts +0 -87
- package/src/commands/runtime-pain-flood-simulation.ts +0 -121
- package/src/commands/runtime-pruning.ts +0 -438
- package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
- package/src/commands/runtime-recovery.ts +0 -109
- package/src/commands/runtime-synthetic-baseline.ts +0 -70
- package/src/commands/runtime-uat.guard.test.ts +0 -175
- package/src/commands/runtime-uat.ts +0 -380
- package/src/commands/runtime.ts +0 -473
- package/src/commands/samples-list.ts +0 -39
- package/src/commands/samples-review.ts +0 -36
- package/src/commands/task.ts +0 -271
- package/src/commands/telemetry.ts +0 -349
- package/src/commands/trace.ts +0 -174
- package/src/commands/trajectory-db-unavailable.ts +0 -19
- package/src/commands/trajectory.ts +0 -64
- package/src/commands/version.ts +0 -49
- package/src/config-reader.ts +0 -122
- package/src/index.ts +0 -1276
- package/src/legacy/legacy-import.ts +0 -179
- package/src/legacy/session-history-import.ts +0 -203
- package/src/resolve-workspace.ts +0 -106
- package/src/services/__tests__/cli-output.test.ts +0 -130
- package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
- package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
- package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
- package/src/services/cli-output.ts +0 -95
- package/src/services/config-doctor.ts +0 -697
- package/src/services/console-launcher.ts +0 -537
- package/src/services/demo-rule-compiler.ts +0 -40
- package/src/services/demo-story-a-runner.ts +0 -471
- package/src/services/mainline-snapshot-assembler.ts +0 -579
- package/src/services/pain-flood-simulation-runner.ts +0 -462
- package/src/services/pd-config-loader.ts +0 -433
- package/src/services/proven-channel-baseline-runner.ts +0 -157
- package/src/services/quality-scorecard/data-extractor.ts +0 -150
- package/src/services/quality-scorecard/local-evaluator.ts +0 -142
- package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
- package/src/services/resolve-runtime-from-pd-config.ts +0 -230
- package/src/services/rulehost-pipeline-runner.ts +0 -1214
- package/src/services/rulehost-readiness.ts +0 -326
- package/src/services/runtime-adapter-resolver.ts +0 -364
- package/src/services/synthetic-baseline-runner.ts +0 -400
- package/src/services/version-report.ts +0 -253
- package/src/services/workspace-tool-semantics.ts +0 -26
- package/src/utils/path-security.ts +0 -96
- package/src/utils/production-workspace-guard.test.ts +0 -157
- package/src/utils/production-workspace-guard.ts +0 -210
- package/tests/bdd/cli-contract.steps.ts +0 -346
- package/tests/bdd/principles-stats.steps.ts +0 -211
- package/tests/bdd/support/gherkin-loader.ts +0 -147
- package/tests/bdd/support/repo-root.ts +0 -62
- package/tests/bdd/support/vitest-bdd.ts +0 -179
- package/tests/commands/admission-gate-coverage.test.ts +0 -79
- package/tests/commands/build-trajectory-evidence.test.ts +0 -508
- package/tests/commands/candidate-audit-repair.test.ts +0 -421
- package/tests/commands/candidate-intake.test.ts +0 -732
- package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
- package/tests/commands/candidate-internalize-lineage.test.ts +0 -628
- package/tests/commands/candidate-internalize.test.ts +0 -299
- package/tests/commands/candidate-route.test.ts +0 -329
- package/tests/commands/candidate-show.test.ts +0 -95
- package/tests/commands/cli-command-tree.test.ts +0 -144
- package/tests/commands/cli-help-snapshot.test.ts +0 -155
- package/tests/commands/cli-skill-contract.test.ts +0 -121
- package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
- package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
- package/tests/commands/codex-setup.test.ts +0 -297
- package/tests/commands/codex-worker-daemon.test.ts +0 -95
- package/tests/commands/codex-worker-registration.test.ts +0 -207
- package/tests/commands/config-doctor.test.ts +0 -336
- package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
- package/tests/commands/console-open.test.ts +0 -1381
- package/tests/commands/context.test.ts +0 -114
- package/tests/commands/demo-story-a.test.ts +0 -318
- package/tests/commands/diagnose.test.ts +0 -1803
- package/tests/commands/errors-list.test.ts +0 -679
- package/tests/commands/evolution-tasks-list.test.ts +0 -41
- package/tests/commands/evolution-tasks-show.test.ts +0 -46
- package/tests/commands/health-codex.test.ts +0 -349
- package/tests/commands/health-host-wiring.test.ts +0 -76
- package/tests/commands/health-reliability.test.ts +0 -99
- package/tests/commands/health.test.ts +0 -413
- package/tests/commands/intent.test.ts +0 -321
- package/tests/commands/legacy-cleanup.test.ts +0 -148
- package/tests/commands/pain-evidence.test.ts +0 -518
- package/tests/commands/pain-list.test.ts +0 -219
- package/tests/commands/pain-record-async.test.ts +0 -335
- package/tests/commands/pain-record-session-parser.test.ts +0 -228
- package/tests/commands/pain-record.test.ts +0 -851
- package/tests/commands/pain-retry.test.ts +0 -1121
- package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
- package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
- package/tests/commands/principles-stats-wiring.test.ts +0 -135
- package/tests/commands/principles-stats.test.ts +0 -274
- package/tests/commands/product-path-regression.test.ts +0 -86
- package/tests/commands/proven-channel-baseline.test.ts +0 -441
- package/tests/commands/rulecode.test.ts +0 -177
- package/tests/commands/run-rulehost-handler.test.ts +0 -908
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
- package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
- package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
- package/tests/commands/runtime-activation.test.ts +0 -2052
- package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
- package/tests/commands/runtime-artifact-repair.test.ts +0 -404
- package/tests/commands/runtime-canary.test.ts +0 -430
- package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
- package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
- package/tests/commands/runtime-features.test.ts +0 -249
- package/tests/commands/runtime-health-snapshot.test.ts +0 -358
- package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
- package/tests/commands/runtime-init.test.ts +0 -420
- package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
- package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
- package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
- package/tests/commands/runtime-internalization-queue.test.ts +0 -380
- package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
- package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
- package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
- package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
- package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
- package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
- package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
- package/tests/commands/runtime-internalization.test.ts +0 -53
- package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
- package/tests/commands/runtime-pruning.test.ts +0 -694
- package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
- package/tests/commands/runtime-recovery.test.ts +0 -120
- package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
- package/tests/commands/runtime-uat.test.ts +0 -397
- package/tests/commands/runtime.test.ts +0 -392
- package/tests/commands/samples-list.test.ts +0 -43
- package/tests/commands/samples-review.test.ts +0 -41
- package/tests/commands/task.test.ts +0 -153
- package/tests/commands/telemetry.test.ts +0 -190
- package/tests/commands/trace.test.ts +0 -316
- package/tests/config-reader.test.ts +0 -143
- package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
- package/tests/e2e/cli-full-flow.test.ts +0 -233
- package/tests/e2e/cross-package-acceptance.test.ts +0 -635
- package/tests/helpers/pd-cli-path.ts +0 -5
- package/tests/pd-cli-smoke.test.ts +0 -13
- package/tests/resolve-workspace.test.ts +0 -196
- package/tests/services/config-doctor-signal-health.test.ts +0 -109
- package/tests/services/console-launcher.test.ts +0 -114
- package/tests/services/demo-rule-compiler.test.ts +0 -242
- package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
- package/tests/services/pd-config-loader.test.ts +0 -505
- package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
- package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
- package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
- package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
- package/tests/services/rulehost-pipeline-runner.test.ts +0 -1242
- package/tests/services/workspace-tool-semantics.test.ts +0 -76
- package/tests/utils/path-security.test.ts +0 -180
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -10
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
|
|
6
|
-
// Mock discoverWorkspaceDefault to prevent real config discovery from interfering
|
|
7
|
-
vi.mock('../../src/services/pd-config-loader.js', async (importOriginal) => {
|
|
8
|
-
const actual = await importOriginal() as Record<string, unknown>;
|
|
9
|
-
return {
|
|
10
|
-
...actual,
|
|
11
|
-
discoverWorkspaceDefault: vi.fn().mockReturnValue(null),
|
|
12
|
-
};
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
resolveRuntimeConfig,
|
|
17
|
-
isRuntimeConfigError,
|
|
18
|
-
validateRuntimeConfig,
|
|
19
|
-
invalidatePainSignalBridge,
|
|
20
|
-
createPainSignalBridge,
|
|
21
|
-
} from '@principles/core/runtime-v2';
|
|
22
|
-
|
|
23
|
-
const stubLedger = {
|
|
24
|
-
readPrincipleSubtree: () => undefined,
|
|
25
|
-
writePrinciple: () => ({ id: 'test' }) as never,
|
|
26
|
-
updatePrincipleValueMetrics: () => ({ principleId: 'test' }) as never,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
describe('PRI-228: PD-owned config resolution cutover', () => {
|
|
30
|
-
describe('PD-owned config consumed by runtime entrypoint', () => {
|
|
31
|
-
let tmpDir: string;
|
|
32
|
-
let stateDir: string;
|
|
33
|
-
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri228-'));
|
|
36
|
-
stateDir = path.join(tmpDir, '.state');
|
|
37
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('resolveRuntimeConfig with explicit workspaceDir resolves pi-ai default', () => {
|
|
45
|
-
const result = resolveRuntimeConfig(stateDir);
|
|
46
|
-
expect(isRuntimeConfigError(result)).toBe(false);
|
|
47
|
-
if (!isRuntimeConfigError(result)) {
|
|
48
|
-
expect(result.runtimeKind).toBe('pi-ai');
|
|
49
|
-
expect(result.timeoutMs).toBeGreaterThan(0);
|
|
50
|
-
expect(result.agentId).toBe('main');
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('resolveRuntimeConfig with openclaw-cli requires explicit openclawMode', () => {
|
|
55
|
-
const result = resolveRuntimeConfig(stateDir, { requestedRuntimeKind: 'openclaw-cli' });
|
|
56
|
-
expect(isRuntimeConfigError(result)).toBe(true);
|
|
57
|
-
if (isRuntimeConfigError(result)) {
|
|
58
|
-
expect(result.reason).toBe('missing_openclaw_mode');
|
|
59
|
-
expect(result.nextAction).toBeTruthy();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('resolveRuntimeConfig with openclaw-cli + openclawLocal resolves correctly', () => {
|
|
64
|
-
const result = resolveRuntimeConfig(stateDir, {
|
|
65
|
-
requestedRuntimeKind: 'openclaw-cli',
|
|
66
|
-
openclawLocal: true,
|
|
67
|
-
});
|
|
68
|
-
expect(isRuntimeConfigError(result)).toBe(false);
|
|
69
|
-
if (!isRuntimeConfigError(result)) {
|
|
70
|
-
expect(result.runtimeKind).toBe('openclaw-cli');
|
|
71
|
-
expect(result.openclawMode).toBe('local');
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('resolveRuntimeConfig does not derive config from idle/night state', () => {
|
|
76
|
-
const result = resolveRuntimeConfig(stateDir);
|
|
77
|
-
expect(isRuntimeConfigError(result)).toBe(false);
|
|
78
|
-
if (!isRuntimeConfigError(result)) {
|
|
79
|
-
expect(Object.keys(result)).not.toContain('idleThreshold');
|
|
80
|
-
expect(Object.keys(result)).not.toContain('triggerMode');
|
|
81
|
-
expect(Object.keys(result)).not.toContain('sleepReflection');
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('Missing explicit config fails loud', () => {
|
|
87
|
-
let tmpDir: string;
|
|
88
|
-
let stateDir: string;
|
|
89
|
-
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri228-fail-'));
|
|
92
|
-
stateDir = path.join(tmpDir, '.state');
|
|
93
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
afterEach(() => {
|
|
97
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('runtime=config without policy returns explicit_config_missing error', () => {
|
|
101
|
-
const result = resolveRuntimeConfig(stateDir, { requestedRuntimeKind: 'config' });
|
|
102
|
-
expect(isRuntimeConfigError(result)).toBe(true);
|
|
103
|
-
if (isRuntimeConfigError(result)) {
|
|
104
|
-
expect(result.reason).toBe('explicit_config_missing');
|
|
105
|
-
expect(result.nextAction).toBeTruthy();
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('error result serializes to single parseable JSON with nextAction', () => {
|
|
110
|
-
const result = resolveRuntimeConfig(stateDir, { requestedRuntimeKind: 'config' });
|
|
111
|
-
expect(isRuntimeConfigError(result)).toBe(true);
|
|
112
|
-
if (isRuntimeConfigError(result)) {
|
|
113
|
-
const json = JSON.stringify(result);
|
|
114
|
-
const parsed = JSON.parse(json);
|
|
115
|
-
expect(parsed.ok).toBe(false);
|
|
116
|
-
expect(parsed.reason).toBeTruthy();
|
|
117
|
-
expect(parsed.message).toBeTruthy();
|
|
118
|
-
expect(parsed.nextAction).toBeTruthy();
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('conflicting openclaw mode returns error with nextAction', () => {
|
|
123
|
-
const result = resolveRuntimeConfig(stateDir, {
|
|
124
|
-
requestedRuntimeKind: 'openclaw-cli',
|
|
125
|
-
openclawLocal: true,
|
|
126
|
-
openclawGateway: true,
|
|
127
|
-
});
|
|
128
|
-
expect(isRuntimeConfigError(result)).toBe(true);
|
|
129
|
-
if (isRuntimeConfigError(result)) {
|
|
130
|
-
expect(result.reason).toBe('conflicting_openclaw_mode');
|
|
131
|
-
expect(result.nextAction).toBeTruthy();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('validateRuntimeConfig throws for openclaw-cli without mode', () => {
|
|
136
|
-
const config = {
|
|
137
|
-
runtimeKind: 'openclaw-cli' as const,
|
|
138
|
-
timeoutMs: 300_000,
|
|
139
|
-
agentId: 'main',
|
|
140
|
-
};
|
|
141
|
-
expect(() => validateRuntimeConfig(config)).not.toThrow();
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('Cache isolation between runtime modes', () => {
|
|
146
|
-
const testWsDir = '/test-pri228-cache-isolation';
|
|
147
|
-
const testStateDir = path.join(testWsDir, '.state');
|
|
148
|
-
|
|
149
|
-
beforeEach(() => {
|
|
150
|
-
invalidatePainSignalBridge(testWsDir);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
afterEach(() => {
|
|
154
|
-
invalidatePainSignalBridge(testWsDir);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it.skip('pi-ai and openclaw-cli produce different bridge instances', async () => {
|
|
158
|
-
const piAiConfig = resolveRuntimeConfig(testStateDir);
|
|
159
|
-
if (isRuntimeConfigError(piAiConfig)) {
|
|
160
|
-
expect.unreachable('pi-ai config should resolve');
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const bridge1 = await createPainSignalBridge({
|
|
164
|
-
workspaceDir: testWsDir,
|
|
165
|
-
stateDir: testStateDir,
|
|
166
|
-
ledgerAdapter: stubLedger,
|
|
167
|
-
});
|
|
168
|
-
expect(bridge1).toBeDefined();
|
|
169
|
-
|
|
170
|
-
const openclawConfig = resolveRuntimeConfig(testStateDir, {
|
|
171
|
-
requestedRuntimeKind: 'openclaw-cli',
|
|
172
|
-
openclawLocal: true,
|
|
173
|
-
});
|
|
174
|
-
if (isRuntimeConfigError(openclawConfig)) {
|
|
175
|
-
expect.unreachable('openclaw-cli config should resolve');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const bridge2 = await createPainSignalBridge({
|
|
179
|
-
workspaceDir: testWsDir,
|
|
180
|
-
stateDir: testStateDir,
|
|
181
|
-
ledgerAdapter: stubLedger,
|
|
182
|
-
});
|
|
183
|
-
expect(bridge2).toBeDefined();
|
|
184
|
-
expect(bridge1).not.toBe(bridge2);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it.skip('invalidatePainSignalBridge with workspace-only clears all modes', async () => {
|
|
188
|
-
const piAiConfig = resolveRuntimeConfig(testStateDir);
|
|
189
|
-
if (isRuntimeConfigError(piAiConfig)) return;
|
|
190
|
-
await createPainSignalBridge({
|
|
191
|
-
workspaceDir: testWsDir,
|
|
192
|
-
stateDir: testStateDir,
|
|
193
|
-
ledgerAdapter: stubLedger,
|
|
194
|
-
});
|
|
195
|
-
invalidatePainSignalBridge(testWsDir);
|
|
196
|
-
const bridgeAfter = await createPainSignalBridge({
|
|
197
|
-
workspaceDir: testWsDir,
|
|
198
|
-
stateDir: testStateDir,
|
|
199
|
-
ledgerAdapter: stubLedger,
|
|
200
|
-
});
|
|
201
|
-
expect(bridgeAfter).toBeDefined();
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('CLI resolveWorkspaceDir gate', () => {
|
|
206
|
-
it('resolveWorkspaceDir throws when no workspace provided', async () => {
|
|
207
|
-
const mod = await import('../../src/resolve-workspace.js');
|
|
208
|
-
delete process.env.PD_WORKSPACE_DIR;
|
|
209
|
-
expect(() => mod.resolveWorkspaceDir()).toThrow(/No workspace directory configured/);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('resolveWorkspaceDir uses explicit workspace when provided', async () => {
|
|
213
|
-
const mod = await import('../../src/resolve-workspace.js');
|
|
214
|
-
const result = mod.resolveWorkspaceDir('/explicit/workspace');
|
|
215
|
-
expect(result).toBe('/explicit/workspace');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('resolveWorkspaceDir uses PD_WORKSPACE_DIR env when no explicit', async () => {
|
|
219
|
-
const mod = await import('../../src/resolve-workspace.js');
|
|
220
|
-
process.env.PD_WORKSPACE_DIR = '/env/workspace';
|
|
221
|
-
try {
|
|
222
|
-
const result = mod.resolveWorkspaceDir();
|
|
223
|
-
expect(result).toBe('/env/workspace');
|
|
224
|
-
} finally {
|
|
225
|
-
delete process.env.PD_WORKSPACE_DIR;
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PRI-393: Runtime config unification tests
|
|
3
|
-
*
|
|
4
|
-
* Validates that all MVP mainline execution paths (probe, run-once, diagnose,
|
|
5
|
-
* pain-retry) read from .pd/config.yaml, NOT from .state/workflows.yaml.
|
|
6
|
-
*
|
|
7
|
-
* ERR refs:
|
|
8
|
-
* - EP-02: production path wiring — tests exercise real production entry points
|
|
9
|
-
* - EP-03: fail loud — no silent fallback
|
|
10
|
-
* - EP-07: runtime state source alignment — doctor/probe/run-once agree
|
|
11
|
-
* - EP-09: test reality gap — production schema fixtures
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
import * as fs from 'node:fs';
|
|
16
|
-
import * as path from 'node:path';
|
|
17
|
-
import * as os from 'node:os';
|
|
18
|
-
import * as yaml from 'js-yaml';
|
|
19
|
-
import {
|
|
20
|
-
assertMainlineContract,
|
|
21
|
-
type MainlineSnapshot,
|
|
22
|
-
} from '@principles/core/runtime-v2';
|
|
23
|
-
|
|
24
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
function mkTmpDir(prefix = 'pri-393-'): string {
|
|
27
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function rmTmpDir(dir: string): void {
|
|
31
|
-
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function writeConfigYaml(workspaceDir: string, content: object): void {
|
|
35
|
-
const pdDir = path.join(workspaceDir, '.pd');
|
|
36
|
-
fs.mkdirSync(pdDir, { recursive: true });
|
|
37
|
-
fs.writeFileSync(
|
|
38
|
-
path.join(pdDir, 'config.yaml'),
|
|
39
|
-
yaml.dump(content, { lineWidth: -1 }),
|
|
40
|
-
'utf8',
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function writeLegacyWorkflowsYaml(workspaceDir: string, content: string): void {
|
|
45
|
-
const stateDir = path.join(workspaceDir, '.state');
|
|
46
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
47
|
-
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), content, 'utf8');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Minimal valid .pd/config.yaml with a pi-ai runtime profile for diagnostician. */
|
|
51
|
-
function makeValidConfigYaml(overrides?: { provider?: string; model?: string }): object {
|
|
52
|
-
return {
|
|
53
|
-
version: 1,
|
|
54
|
-
features: {
|
|
55
|
-
prompt: { enabled: true, category: 'core' },
|
|
56
|
-
correction_observer: { enabled: false, category: 'quiet' },
|
|
57
|
-
},
|
|
58
|
-
runtimeProfiles: {
|
|
59
|
-
lmstudio: {
|
|
60
|
-
type: 'pi-ai',
|
|
61
|
-
provider: overrides?.provider ?? 'lmstudio',
|
|
62
|
-
model: overrides?.model ?? 'local-model',
|
|
63
|
-
apiKeyEnv: 'LMSTUDIO_API_KEY',
|
|
64
|
-
baseUrl: 'http://localhost:1234/v1',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
internalAgents: {
|
|
68
|
-
defaultRuntime: 'lmstudio',
|
|
69
|
-
agents: {
|
|
70
|
-
diagnostician: {
|
|
71
|
-
enabled: true,
|
|
72
|
-
runtimeProfile: 'lmstudio',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Tests ──────────────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
describe('PRI-393: runtime config unification', () => {
|
|
82
|
-
let tmpDir: string;
|
|
83
|
-
|
|
84
|
-
beforeEach(() => {
|
|
85
|
-
tmpDir = mkTmpDir();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
afterEach(() => {
|
|
89
|
-
rmTmpDir(tmpDir);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('Guard: legacy resolveRuntimeConfig not imported by production commands', () => {
|
|
93
|
-
it('production command source files do NOT import legacy resolveRuntimeConfig', () => {
|
|
94
|
-
// Read the source of each production command and verify the import
|
|
95
|
-
const commandFiles = [
|
|
96
|
-
'packages/pd-cli/src/commands/runtime.ts',
|
|
97
|
-
'packages/pd-cli/src/commands/runtime-internalization-run-once.ts',
|
|
98
|
-
'packages/pd-cli/src/commands/diagnose.ts',
|
|
99
|
-
'packages/pd-cli/src/commands/pain-retry.ts',
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
for (const file of commandFiles) {
|
|
103
|
-
// CWE-22: resolve against the repo root (this test file lives at
|
|
104
|
-
// packages/pd-cli/tests/commands/) and refuse paths that escape it.
|
|
105
|
-
const repoRoot = path.resolve(__dirname, '../../..');
|
|
106
|
-
const fullPath = path.resolve(repoRoot, file);
|
|
107
|
-
if (!fullPath.startsWith(repoRoot + path.sep)) continue;
|
|
108
|
-
if (!fs.existsSync(fullPath)) continue;
|
|
109
|
-
const source = fs.readFileSync(fullPath, 'utf8');
|
|
110
|
-
|
|
111
|
-
// Check that resolveRuntimeConfig is NOT imported from @principles/core/runtime-v2
|
|
112
|
-
// (it may appear in comments or as resolveRuntimeConfigFromPdConfig)
|
|
113
|
-
// Match the full import block (handles multi-line imports)
|
|
114
|
-
const importPattern = /import\s*[\s\S]*?@principles\/core\/runtime-v2['";]/g;
|
|
115
|
-
const importBlocks: string[] = [];
|
|
116
|
-
let match;
|
|
117
|
-
while ((match = importPattern.exec(source)) !== null) {
|
|
118
|
-
importBlocks.push(match[0]);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
for (const block of importBlocks) {
|
|
122
|
-
// Allow the canonical resolver family — resolveRuntimeConfigFromPdConfig
|
|
123
|
-
// (shared diagnostician binding) and resolveRuntimeConfigForAgent (the
|
|
124
|
-
// PRI-719 per-agent variant) — but NOT bare legacy resolveRuntimeConfig
|
|
125
|
-
if (block.includes('resolveRuntimeConfig') && !block.includes('resolveRuntimeConfigFromPdConfig') && !block.includes('resolveRuntimeConfigForAgent')) {
|
|
126
|
-
// This is the legacy import — fail
|
|
127
|
-
expect.fail(
|
|
128
|
-
`${file} still imports legacy resolveRuntimeConfig from @principles/core/runtime-v2. ` +
|
|
129
|
-
`Use resolveRuntimeFromPdConfig() from services/resolve-runtime-from-pd-config.ts instead.`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('Config source alignment via mainline contract', () => {
|
|
138
|
-
function makeBaseSnapshot(readiness: Partial<MainlineSnapshot['readiness']>): MainlineSnapshot {
|
|
139
|
-
return {
|
|
140
|
-
readiness: {
|
|
141
|
-
configDoctorProfile: null,
|
|
142
|
-
runtimeProbeProfile: null,
|
|
143
|
-
configSource: '.pd/config.yaml',
|
|
144
|
-
probeConfigSource: '.pd/config.yaml',
|
|
145
|
-
diagnosticianReady: true,
|
|
146
|
-
...readiness,
|
|
147
|
-
},
|
|
148
|
-
chain: {
|
|
149
|
-
painId: null,
|
|
150
|
-
diagnosisTask: null,
|
|
151
|
-
diagnosticianArtifact: null,
|
|
152
|
-
candidate: null,
|
|
153
|
-
dreamerTask: null,
|
|
154
|
-
dreamerContext: null,
|
|
155
|
-
successor: null,
|
|
156
|
-
principle: null,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
it('violation: doctor and probe use different config sources (drift)', () => {
|
|
162
|
-
const snapshot = makeBaseSnapshot({
|
|
163
|
-
configDoctorProfile: 'pi-ai.lmstudio',
|
|
164
|
-
runtimeProbeProfile: 'pi-ai.sensenova-cn',
|
|
165
|
-
configSource: '.pd/config.yaml',
|
|
166
|
-
probeConfigSource: '.state/workflows.yaml',
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const verdict = assertMainlineContract(snapshot);
|
|
170
|
-
const alignmentStage = verdict.stages.find((s) => s.stage === 'config_source_alignment');
|
|
171
|
-
|
|
172
|
-
expect(alignmentStage).toBeDefined();
|
|
173
|
-
expect(alignmentStage!.status).toBe('violation');
|
|
174
|
-
expect(alignmentStage!.reason).toContain('drift');
|
|
175
|
-
expect(alignmentStage!.nextAction).toContain('.pd/config.yaml');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('violation: profiles match but probe reads from workflows.yaml (coincidental)', () => {
|
|
179
|
-
const snapshot = makeBaseSnapshot({
|
|
180
|
-
configDoctorProfile: 'pi-ai.lmstudio',
|
|
181
|
-
runtimeProbeProfile: 'pi-ai.lmstudio',
|
|
182
|
-
configSource: '.pd/config.yaml',
|
|
183
|
-
probeConfigSource: '.state/workflows.yaml',
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const verdict = assertMainlineContract(snapshot);
|
|
187
|
-
const alignmentStage = verdict.stages.find((s) => s.stage === 'config_source_alignment');
|
|
188
|
-
|
|
189
|
-
expect(alignmentStage).toBeDefined();
|
|
190
|
-
expect(alignmentStage!.status).toBe('violation');
|
|
191
|
-
expect(alignmentStage!.reason).toContain('coincidental');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('ok: doctor and probe agree on same profile from .pd/config.yaml', () => {
|
|
195
|
-
const snapshot = makeBaseSnapshot({
|
|
196
|
-
configDoctorProfile: 'pi-ai.lmstudio',
|
|
197
|
-
runtimeProbeProfile: 'pi-ai.lmstudio',
|
|
198
|
-
configSource: '.pd/config.yaml',
|
|
199
|
-
probeConfigSource: '.pd/config.yaml',
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const verdict = assertMainlineContract(snapshot);
|
|
203
|
-
const alignmentStage = verdict.stages.find((s) => s.stage === 'config_source_alignment');
|
|
204
|
-
|
|
205
|
-
expect(alignmentStage).toBeDefined();
|
|
206
|
-
expect(alignmentStage!.status).toBe('ok');
|
|
207
|
-
expect(alignmentStage!.reason).toContain('.pd/config.yaml');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe('resolveRuntimeFromPdConfig reads .pd/config.yaml', () => {
|
|
212
|
-
it('resolves pi-ai config from .pd/config.yaml', async () => {
|
|
213
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
214
|
-
|
|
215
|
-
// Dynamically import to avoid module resolution issues
|
|
216
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
217
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
218
|
-
|
|
219
|
-
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
220
|
-
expect(resolved.result).toBeDefined();
|
|
221
|
-
// Should not be an error when config is valid
|
|
222
|
-
const { isRuntimeConfigError: isErr } = await import('@principles/core/runtime-v2');
|
|
223
|
-
expect(isErr(resolved.result)).toBe(false);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('ignores conflicting .state/workflows.yaml when .pd/config.yaml exists', async () => {
|
|
227
|
-
// Write .pd/config.yaml with lmstudio
|
|
228
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml({ provider: 'lmstudio', model: 'local-model' }));
|
|
229
|
-
|
|
230
|
-
// Write conflicting .state/workflows.yaml
|
|
231
|
-
writeLegacyWorkflowsYaml(tmpDir, `version: '1'
|
|
232
|
-
funnels:
|
|
233
|
-
- workflowId: pd-runtime-v2-diagnosis
|
|
234
|
-
stages: []
|
|
235
|
-
policy:
|
|
236
|
-
runtimeKind: pi-ai
|
|
237
|
-
provider: sensenova-cn
|
|
238
|
-
model: deepseek-v4-flash
|
|
239
|
-
apiKeyEnv: SENSENOVA_API_KEY
|
|
240
|
-
`);
|
|
241
|
-
|
|
242
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
243
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
244
|
-
|
|
245
|
-
// Should have legacy warning
|
|
246
|
-
expect(resolved.legacyWarnings.length).toBeGreaterThan(0);
|
|
247
|
-
expect(resolved.legacyWarnings[0]).toContain('workflows.yaml');
|
|
248
|
-
|
|
249
|
-
// Should resolve from .pd/config.yaml, NOT workflows.yaml
|
|
250
|
-
const { isRuntimeConfigError: isErr } = await import('@principles/core/runtime-v2');
|
|
251
|
-
if (!isErr(resolved.result)) {
|
|
252
|
-
expect(resolved.result.provider).not.toBe('sensenova-cn');
|
|
253
|
-
}
|
|
254
|
-
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('fail loud when .pd/config.yaml is malformed', async () => {
|
|
258
|
-
const pdDir = path.join(tmpDir, '.pd');
|
|
259
|
-
fs.mkdirSync(pdDir, { recursive: true });
|
|
260
|
-
fs.writeFileSync(path.join(pdDir, 'config.yaml'), 'version: [unterminated', 'utf8');
|
|
261
|
-
|
|
262
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
263
|
-
const { isRuntimeConfigError: isErr } = await import('@principles/core/runtime-v2');
|
|
264
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
265
|
-
|
|
266
|
-
// Malformed config must produce a RuntimeConfigError — never fall back to defaults
|
|
267
|
-
expect(resolved.configLoadResult.ok).toBe(false);
|
|
268
|
-
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
269
|
-
expect(isErr(resolved.result)).toBe(true);
|
|
270
|
-
if (isErr(resolved.result)) {
|
|
271
|
-
expect(resolved.result.reason).toContain('config_malformed');
|
|
272
|
-
expect(resolved.result.nextAction).toBeTruthy();
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
describe('JSON output purity', () => {
|
|
278
|
-
it('resolveRuntimeFromPdConfig result serializes to valid JSON', async () => {
|
|
279
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
280
|
-
|
|
281
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
282
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
283
|
-
|
|
284
|
-
const jsonStr = JSON.stringify(resolved.result);
|
|
285
|
-
const parsed = JSON.parse(jsonStr);
|
|
286
|
-
expect(parsed).toBeDefined();
|
|
287
|
-
expect(typeof parsed).toBe('object');
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// ── Boundary Condition Tests ────────────────────────────────────────────────
|
|
292
|
-
|
|
293
|
-
describe('resolveRuntimeWithOverrides', () => {
|
|
294
|
-
it('CLI overrides take precedence over config values', async () => {
|
|
295
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml({ provider: 'lmstudio', model: 'local-model' }));
|
|
296
|
-
|
|
297
|
-
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
298
|
-
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
299
|
-
provider: 'override-provider',
|
|
300
|
-
model: 'override-model',
|
|
301
|
-
}, () => 'test-key');
|
|
302
|
-
|
|
303
|
-
expect(resolved.mergedConfig).not.toBeNull();
|
|
304
|
-
if (resolved.mergedConfig) {
|
|
305
|
-
expect(resolved.mergedConfig.provider).toBe('override-provider');
|
|
306
|
-
expect(resolved.mergedConfig.model).toBe('override-model');
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('returns mergedConfig=null when base config is malformed', async () => {
|
|
311
|
-
const pdDir = path.join(tmpDir, '.pd');
|
|
312
|
-
fs.mkdirSync(pdDir, { recursive: true });
|
|
313
|
-
fs.writeFileSync(path.join(pdDir, 'config.yaml'), 'version: [invalid', 'utf8');
|
|
314
|
-
|
|
315
|
-
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
316
|
-
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
317
|
-
provider: 'override',
|
|
318
|
-
}, () => 'test-key');
|
|
319
|
-
|
|
320
|
-
expect(resolved.mergedConfig).toBeNull();
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('preserves config values when overrides are undefined', async () => {
|
|
324
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml({ provider: 'lmstudio', model: 'local-model' }));
|
|
325
|
-
|
|
326
|
-
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
327
|
-
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
328
|
-
provider: undefined,
|
|
329
|
-
model: undefined,
|
|
330
|
-
}, () => 'test-key');
|
|
331
|
-
|
|
332
|
-
expect(resolved.mergedConfig).not.toBeNull();
|
|
333
|
-
if (resolved.mergedConfig) {
|
|
334
|
-
expect(resolved.mergedConfig.provider).toBe('lmstudio');
|
|
335
|
-
expect(resolved.mergedConfig.model).toBe('local-model');
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('merges numeric overrides correctly', async () => {
|
|
340
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
341
|
-
|
|
342
|
-
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
343
|
-
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
344
|
-
maxRetries: 5,
|
|
345
|
-
timeoutMs: 60000,
|
|
346
|
-
}, () => 'test-key');
|
|
347
|
-
|
|
348
|
-
expect(resolved.mergedConfig).not.toBeNull();
|
|
349
|
-
if (resolved.mergedConfig) {
|
|
350
|
-
expect(resolved.mergedConfig.maxRetries).toBe(5);
|
|
351
|
-
expect(resolved.mergedConfig.timeoutMs).toBe(60000);
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('edge cases and error handling', () => {
|
|
357
|
-
it('handles empty workspace directory gracefully', async () => {
|
|
358
|
-
// No .pd directory at all
|
|
359
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
360
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
361
|
-
|
|
362
|
-
// Missing config should return defaults (ok=true with source='defaults')
|
|
363
|
-
expect(resolved.configLoadResult.ok).toBe(true);
|
|
364
|
-
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('handles config with missing runtimeProfiles section', async () => {
|
|
368
|
-
writeConfigYaml(tmpDir, {
|
|
369
|
-
version: 1,
|
|
370
|
-
features: {},
|
|
371
|
-
// Missing runtimeProfiles
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
375
|
-
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
376
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
377
|
-
|
|
378
|
-
// Missing runtimeProfiles should either load with defaults or produce
|
|
379
|
-
// a RuntimeConfigError — never silently succeed with wrong config.
|
|
380
|
-
if (isRuntimeConfigError(resolved.result)) {
|
|
381
|
-
expect(resolved.result.reason).toBeTruthy();
|
|
382
|
-
expect(resolved.result.nextAction).toBeTruthy();
|
|
383
|
-
} else {
|
|
384
|
-
// If defaults were returned, configLoadResult.ok should be true
|
|
385
|
-
expect(resolved.configLoadResult.ok).toBe(true);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('handles config with invalid runtime profile type', async () => {
|
|
390
|
-
writeConfigYaml(tmpDir, {
|
|
391
|
-
version: 1,
|
|
392
|
-
runtimeProfiles: {
|
|
393
|
-
'bad-profile': {
|
|
394
|
-
type: 'invalid-type', // Invalid type
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
internalAgents: {
|
|
398
|
-
defaultRuntime: 'bad-profile',
|
|
399
|
-
agents: { diagnostician: { enabled: true, runtimeProfile: 'bad-profile' } },
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
404
|
-
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
405
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
406
|
-
|
|
407
|
-
// Invalid type should be caught — verify external contract, not internal state
|
|
408
|
-
expect(isRuntimeConfigError(resolved.result)).toBe(true);
|
|
409
|
-
if (isRuntimeConfigError(resolved.result)) {
|
|
410
|
-
expect(resolved.result.reason).toBeTruthy();
|
|
411
|
-
expect(resolved.result.nextAction).toBeTruthy();
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it('handles env var returning undefined for apiKeyEnv', async () => {
|
|
416
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
417
|
-
|
|
418
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
419
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => undefined });
|
|
420
|
-
|
|
421
|
-
// Should handle undefined env var gracefully
|
|
422
|
-
expect(resolved.result).toBeDefined();
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
it('handles multiple legacy files detected', async () => {
|
|
426
|
-
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
427
|
-
|
|
428
|
-
// Create multiple legacy files
|
|
429
|
-
const stateDir = path.join(tmpDir, '.state');
|
|
430
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
431
|
-
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1', 'utf8');
|
|
432
|
-
|
|
433
|
-
const ffDir = path.join(tmpDir, '.pd');
|
|
434
|
-
fs.writeFileSync(path.join(ffDir, 'feature-flags.yaml'), 'flags: []', 'utf8');
|
|
435
|
-
|
|
436
|
-
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
437
|
-
const resolved = resolveRuntimeFromPdConfig(tmpDir, { getEnvVar: () => 'test-key' });
|
|
438
|
-
|
|
439
|
-
expect(resolved.legacyWarnings.length).toBeGreaterThan(0);
|
|
440
|
-
expect(resolved.configLoadResult.legacyFilesDetected.length).toBeGreaterThanOrEqual(2);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
});
|