@principles/pd-cli 1.133.0 → 1.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/__tests__/intent-flag-wiring.test.js +74 -3
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
- package/dist/commands/admission-gate.d.ts +24 -0
- package/dist/commands/admission-gate.d.ts.map +1 -0
- package/dist/commands/admission-gate.js +29 -0
- package/dist/commands/admission-gate.js.map +1 -0
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +72 -6
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +40 -0
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/demo-story-a.d.ts.map +1 -1
- package/dist/commands/demo-story-a.js +4 -2
- package/dist/commands/demo-story-a.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +155 -8
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/errors-list.d.ts +25 -0
- package/dist/commands/errors-list.d.ts.map +1 -0
- package/dist/commands/errors-list.js +368 -0
- package/dist/commands/errors-list.js.map +1 -0
- package/dist/commands/health-codex.d.ts +7 -0
- package/dist/commands/health-codex.d.ts.map +1 -0
- package/dist/commands/health-codex.js +209 -0
- package/dist/commands/health-codex.js.map +1 -0
- package/dist/commands/health.d.ts +24 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +48 -0
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/intent.d.ts +21 -4
- package/dist/commands/intent.d.ts.map +1 -1
- package/dist/commands/intent.js +88 -27
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/pain-evidence.d.ts +6 -0
- package/dist/commands/pain-evidence.d.ts.map +1 -1
- package/dist/commands/pain-evidence.js +6 -0
- package/dist/commands/pain-evidence.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +21 -0
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/pain-retry.d.ts +4 -0
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +327 -32
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
- package/dist/commands/proven-channel-baseline.js +4 -2
- package/dist/commands/proven-channel-baseline.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts +30 -0
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +567 -11
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-canary.d.ts.map +1 -1
- package/dist/commands/runtime-canary.js +32 -7
- package/dist/commands/runtime-canary.js.map +1 -1
- package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
- package/dist/commands/runtime-health-snapshot.js +4 -2
- package/dist/commands/runtime-health-snapshot.js.map +1 -1
- package/dist/commands/runtime-init.d.ts +43 -0
- package/dist/commands/runtime-init.d.ts.map +1 -0
- package/dist/commands/runtime-init.js +264 -0
- package/dist/commands/runtime-init.js.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.js +374 -0
- package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
- package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-integrity.js +4 -2
- package/dist/commands/runtime-internalization-integrity.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +20 -5
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/commands/runtime-uat.d.ts.map +1 -1
- package/dist/commands/runtime-uat.guard.test.js +13 -15
- package/dist/commands/runtime-uat.guard.test.js.map +1 -1
- package/dist/commands/runtime-uat.js +4 -3
- package/dist/commands/runtime-uat.js.map +1 -1
- package/dist/config-reader.js +1 -1
- package/dist/config-reader.js.map +1 -1
- package/dist/index.js +54 -78
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
- package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/config-doctor.d.ts.map +1 -1
- package/dist/services/config-doctor.js +9 -2
- package/dist/services/config-doctor.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +9 -0
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/pd-config-loader.js +2 -2
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
- package/dist/services/proven-channel-baseline-runner.js +10 -2
- package/dist/services/proven-channel-baseline-runner.js.map +1 -1
- package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +111 -7
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/dist/services/rulehost-readiness.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +1 -0
- package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/dist/services/runtime-adapter-resolver.js +3 -0
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +11 -0
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/dist/utils/production-workspace-guard.d.ts +4 -4
- package/dist/utils/production-workspace-guard.d.ts.map +1 -1
- package/dist/utils/production-workspace-guard.js +19 -16
- package/dist/utils/production-workspace-guard.js.map +1 -1
- package/dist/utils/production-workspace-guard.test.js +54 -19
- package/dist/utils/production-workspace-guard.test.js.map +1 -1
- package/package.json +8 -5
- package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
- package/src/commands/admission-gate.ts +30 -0
- package/src/commands/candidate.ts +74 -6
- package/src/commands/console.ts +40 -0
- package/src/commands/demo-story-a.ts +4 -2
- package/src/commands/diagnose.ts +158 -10
- package/src/commands/errors-list.ts +440 -0
- package/src/commands/health-codex.ts +249 -0
- package/src/commands/health.ts +60 -0
- package/src/commands/intent.ts +98 -29
- package/src/commands/pain-evidence.ts +6 -0
- package/src/commands/pain-record.ts +21 -0
- package/src/commands/pain-retry.ts +353 -35
- package/src/commands/proven-channel-baseline.ts +4 -2
- package/src/commands/runtime-activation.ts +657 -12
- package/src/commands/runtime-canary.ts +31 -5
- package/src/commands/runtime-health-snapshot.ts +4 -2
- package/src/commands/runtime-init.ts +305 -0
- package/src/commands/runtime-internalization-context-trace.ts +445 -0
- package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
- package/src/commands/runtime-internalization-integrity.ts +4 -2
- package/src/commands/runtime-internalization-run-once.ts +20 -5
- package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
- package/src/commands/runtime-uat.guard.test.ts +14 -16
- package/src/commands/runtime-uat.ts +4 -3
- package/src/config-reader.ts +1 -1
- package/src/index.ts +69 -78
- package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
- package/src/services/config-doctor.ts +9 -2
- package/src/services/pain-flood-simulation-runner.ts +9 -0
- package/src/services/pd-config-loader.ts +2 -2
- package/src/services/proven-channel-baseline-runner.ts +10 -3
- package/src/services/rulehost-pipeline-runner.ts +157 -6
- package/src/services/runtime-adapter-resolver.ts +4 -0
- package/src/services/synthetic-baseline-runner.ts +11 -0
- package/src/utils/production-workspace-guard.test.ts +62 -20
- package/src/utils/production-workspace-guard.ts +19 -16
- package/tests/bdd/cli-contract.steps.ts +343 -0
- package/tests/bdd/context-trace.steps.ts +229 -0
- package/tests/bdd/support/gherkin-loader.ts +147 -0
- package/tests/bdd/support/repo-root.ts +62 -0
- package/tests/bdd/support/vitest-bdd.ts +179 -0
- package/tests/commands/admission-gate-coverage.test.ts +79 -0
- package/tests/commands/candidate-audit-repair.test.ts +77 -0
- package/tests/commands/candidate-intake.test.ts +155 -10
- package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
- package/tests/commands/cli-help-snapshot.test.ts +15 -1
- package/tests/commands/console-open.test.ts +19 -0
- package/tests/commands/diagnose.test.ts +619 -14
- package/tests/commands/errors-list.test.ts +678 -0
- package/tests/commands/health-codex.test.ts +243 -0
- package/tests/commands/health-host-wiring.test.ts +76 -0
- package/tests/commands/intent.test.ts +4 -4
- package/tests/commands/pain-record.test.ts +80 -0
- package/tests/commands/pain-retry.test.ts +94 -3
- package/tests/commands/run-rulehost-handler.test.ts +133 -0
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
- package/tests/commands/runtime-activation.test.ts +1090 -5
- package/tests/commands/runtime-canary.test.ts +50 -0
- package/tests/commands/runtime-features.test.ts +2 -0
- package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
- package/tests/commands/runtime-init.test.ts +346 -0
- package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
- package/tests/commands/runtime-internalization.test.ts +2 -2
- package/tests/config-reader.test.ts +6 -5
- package/tests/e2e/cross-package-acceptance.test.ts +55 -13
- package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
- package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
- package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
- package/vitest.config.ts +1 -1
|
@@ -96,6 +96,18 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
96
96
|
}),
|
|
97
97
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
98
98
|
PrincipleTreeLedgerAdapter: MockPrincipleTreeLedgerAdapter,
|
|
99
|
+
evaluateCandidateAdmissionFromRecord: vi.fn((candidate: { recommendationKind: string; confidence: number | null }) => {
|
|
100
|
+
if (candidate.recommendationKind === 'defer') {
|
|
101
|
+
return { decision: 'deferred', reason: 'recommendation_kind_defer_not_actionable', nextAction: 'review_defer_disposition_manually', evidenceStatus: 'unknown' };
|
|
102
|
+
}
|
|
103
|
+
if (candidate.confidence === null) {
|
|
104
|
+
return { decision: 'needs_evidence', reason: 'confidence_missing_on_candidate_record', nextAction: 're_run_diagnosis_to_populate_confidence_or_manual_review', evidenceStatus: 'unknown' };
|
|
105
|
+
}
|
|
106
|
+
if (candidate.confidence !== undefined && candidate.confidence < 0.5) {
|
|
107
|
+
return { decision: 'needs_evidence', reason: `confidence_below_threshold:${candidate.confidence.toFixed(2)}<0.5`, nextAction: 'provide_additional_evidence_or_manual_review', evidenceStatus: 'unknown' };
|
|
108
|
+
}
|
|
109
|
+
return { decision: 'admitted', reason: 'cli_partial_check_passed_confidence_and_kind', nextAction: 'none', evidenceStatus: 'unknown' };
|
|
110
|
+
}),
|
|
99
111
|
}));
|
|
100
112
|
|
|
101
113
|
// PRI-443 Phase 5: loadLedger and getLedgerFilePathPublic now imported from
|
|
@@ -341,4 +353,69 @@ describe('pd candidate repair', () => {
|
|
|
341
353
|
);
|
|
342
354
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
343
355
|
});
|
|
356
|
+
|
|
357
|
+
// PRI-442 Stage 4: admission gate bypass prevention for repair
|
|
358
|
+
describe('admission gate bypass prevention (PRI-442 Stage 4)', () => {
|
|
359
|
+
it('refuses repair when recommendationKind is defer', async () => {
|
|
360
|
+
mockStateManager.getCandidate.mockResolvedValue({
|
|
361
|
+
candidateId: 'c-defer',
|
|
362
|
+
status: 'consumed',
|
|
363
|
+
artifactId: 'a1',
|
|
364
|
+
title: 'Deferred',
|
|
365
|
+
description: 'desc',
|
|
366
|
+
recommendationKind: 'defer',
|
|
367
|
+
confidence: 0.9,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await handleCandidateRepair({ candidateId: 'c-defer', workspace: '/tmp/test-workspace', json: true });
|
|
371
|
+
|
|
372
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
373
|
+
expect.stringContaining('"status": "refused"'),
|
|
374
|
+
);
|
|
375
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
376
|
+
expect.stringContaining('"admissionDecision": "deferred"'),
|
|
377
|
+
);
|
|
378
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('refuses repair when confidence is null (rc-3 fail loud)', async () => {
|
|
382
|
+
mockStateManager.getCandidate.mockResolvedValue({
|
|
383
|
+
candidateId: 'c-null',
|
|
384
|
+
status: 'consumed',
|
|
385
|
+
artifactId: 'a1',
|
|
386
|
+
title: 'Null conf',
|
|
387
|
+
description: 'desc',
|
|
388
|
+
recommendationKind: 'principle',
|
|
389
|
+
confidence: null,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await handleCandidateRepair({ candidateId: 'c-null', workspace: '/tmp/test-workspace', json: false });
|
|
393
|
+
|
|
394
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
395
|
+
expect.stringContaining('Admission gate refused'),
|
|
396
|
+
);
|
|
397
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
398
|
+
expect.stringContaining('needs_evidence'),
|
|
399
|
+
);
|
|
400
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('allows repair when candidate passes admission gate', async () => {
|
|
404
|
+
mockStateManager.getCandidate.mockResolvedValue({
|
|
405
|
+
candidateId: 'c-ok',
|
|
406
|
+
status: 'consumed',
|
|
407
|
+
artifactId: 'a1',
|
|
408
|
+
title: 'OK',
|
|
409
|
+
description: 'desc',
|
|
410
|
+
recommendationKind: 'principle',
|
|
411
|
+
confidence: 0.8,
|
|
412
|
+
});
|
|
413
|
+
mockAdapter.existsForCandidate.mockReturnValue(null);
|
|
414
|
+
mockService.intake.mockResolvedValue({ id: 'entry-1', title: 'OK', text: 'text', status: 'probation' });
|
|
415
|
+
|
|
416
|
+
await handleCandidateRepair({ candidateId: 'c-ok', workspace: '/tmp/test-workspace', json: true });
|
|
417
|
+
|
|
418
|
+
expect(mockService.intake).toHaveBeenCalledWith('c-ok');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
344
421
|
});
|
|
@@ -72,6 +72,19 @@ vi.mock('@principles/core/runtime-v2', () => ({
|
|
|
72
72
|
RuntimeStateManager: MockRuntimeStateManager,
|
|
73
73
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
74
74
|
PrincipleTreeLedgerAdapter: MockPrincipleTreeLedgerAdapter,
|
|
75
|
+
evaluateCandidateAdmissionFromRecord: vi.fn((candidate: { recommendationKind: string; confidence: number | null }) => {
|
|
76
|
+
// Mirror the real implementation for test purposes
|
|
77
|
+
if (candidate.recommendationKind === 'defer') {
|
|
78
|
+
return { decision: 'deferred', reason: 'recommendation_kind_defer_not_actionable', nextAction: 'review_defer_disposition_manually', evidenceStatus: 'unknown' };
|
|
79
|
+
}
|
|
80
|
+
if (candidate.confidence === null) {
|
|
81
|
+
return { decision: 'needs_evidence', reason: 'confidence_missing_on_candidate_record', nextAction: 're_run_diagnosis_to_populate_confidence_or_manual_review', evidenceStatus: 'unknown' };
|
|
82
|
+
}
|
|
83
|
+
if (candidate.confidence < 0.5) {
|
|
84
|
+
return { decision: 'needs_evidence', reason: `confidence_below_threshold:${candidate.confidence.toFixed(2)}<0.5`, nextAction: 'provide_additional_evidence_or_manual_review', evidenceStatus: 'unknown' };
|
|
85
|
+
}
|
|
86
|
+
return { decision: 'admitted', reason: 'cli_partial_check_passed_confidence_and_kind', nextAction: 'none', evidenceStatus: 'unknown' };
|
|
87
|
+
}),
|
|
75
88
|
}));
|
|
76
89
|
|
|
77
90
|
vi.mock('../../src/resolve-workspace.js', () => ({
|
|
@@ -348,29 +361,37 @@ describe('pd candidate intake', () => {
|
|
|
348
361
|
|
|
349
362
|
// Test 4: Non-existent candidate
|
|
350
363
|
it('Test 4 (non-existent candidate): throws CANDIDATE_NOT_FOUND error', async () => {
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
'CANDIDATE_NOT_FOUND',
|
|
356
|
-
{ candidateId: 'invalid-id' }
|
|
357
|
-
);
|
|
358
|
-
});
|
|
364
|
+
// PRI-442 Stage 4: getCandidate now returns null before service.intake is called.
|
|
365
|
+
// The CLI exits with "Candidate not found" instead of relying on service.intake
|
|
366
|
+
// to throw CANDIDATE_NOT_FOUND.
|
|
367
|
+
mockStateManager.getCandidate.mockResolvedValue(null);
|
|
359
368
|
|
|
360
369
|
await handleCandidateIntake({
|
|
361
370
|
candidateId: 'invalid-id',
|
|
362
371
|
workspace: '/tmp/test-workspace',
|
|
363
372
|
});
|
|
364
373
|
|
|
365
|
-
// Verify error output contains
|
|
374
|
+
// Verify error output contains "Candidate not found"
|
|
366
375
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
367
|
-
expect.stringContaining('
|
|
376
|
+
expect.stringContaining('Candidate not found')
|
|
368
377
|
);
|
|
369
378
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
370
379
|
});
|
|
371
380
|
|
|
372
381
|
// Test 5: Invalid candidateId (empty string)
|
|
373
382
|
it('Test 5 (invalid candidateId): empty string throws INPUT_INVALID error', async () => {
|
|
383
|
+
// PRI-442 Stage 4: getCandidate is called before service.intake, so we need
|
|
384
|
+
// to mock it to return a valid candidate so service.intake is reached.
|
|
385
|
+
const mockCandidate = {
|
|
386
|
+
candidateId: '',
|
|
387
|
+
artifactId: 'artifact-1',
|
|
388
|
+
taskId: 'task-1',
|
|
389
|
+
title: 'Test',
|
|
390
|
+
description: 'Test',
|
|
391
|
+
status: 'pending',
|
|
392
|
+
};
|
|
393
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
394
|
+
|
|
374
395
|
// Setup: service.intake throws INPUT_INVALID for empty candidateId
|
|
375
396
|
mockService.intake.mockImplementation(() => {
|
|
376
397
|
throw new MockCandidateIntakeError(
|
|
@@ -475,6 +496,18 @@ describe('pd candidate intake', () => {
|
|
|
475
496
|
|
|
476
497
|
// Test 7: Generic error (non-CandidateIntakeError, covers line 247)
|
|
477
498
|
it('Test 7 (generic error): handles non-CandidateIntakeError errors', async () => {
|
|
499
|
+
// PRI-442 Stage 4: getCandidate is called before service.intake, so we need
|
|
500
|
+
// to mock it to return a valid candidate so service.intake is reached.
|
|
501
|
+
const mockCandidate = {
|
|
502
|
+
candidateId: 'valid-id',
|
|
503
|
+
artifactId: 'artifact-1',
|
|
504
|
+
taskId: 'task-1',
|
|
505
|
+
title: 'Test',
|
|
506
|
+
description: 'Test',
|
|
507
|
+
status: 'pending',
|
|
508
|
+
};
|
|
509
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
510
|
+
|
|
478
511
|
// Setup: service.intake throws a generic Error (not CandidateIntakeError)
|
|
479
512
|
mockService.intake.mockImplementation(() => {
|
|
480
513
|
throw new Error('Some unexpected error');
|
|
@@ -584,4 +617,116 @@ describe('pd candidate intake', () => {
|
|
|
584
617
|
mockStateManager.connection = originalConnection;
|
|
585
618
|
}
|
|
586
619
|
});
|
|
620
|
+
|
|
621
|
+
// PRI-442 Stage 4: admission gate bypass tests
|
|
622
|
+
describe('admission gate bypass prevention (PRI-442 Stage 4)', () => {
|
|
623
|
+
it('refuses intake when recommendationKind is defer', async () => {
|
|
624
|
+
const mockCandidate = {
|
|
625
|
+
candidateId: 'defer-cand',
|
|
626
|
+
artifactId: 'artifact-1',
|
|
627
|
+
taskId: 'task-1',
|
|
628
|
+
title: 'Deferred candidate',
|
|
629
|
+
description: 'Should not be intakeable',
|
|
630
|
+
status: 'pending',
|
|
631
|
+
recommendationKind: 'defer',
|
|
632
|
+
confidence: 0.9,
|
|
633
|
+
};
|
|
634
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
635
|
+
|
|
636
|
+
await handleCandidateIntake({
|
|
637
|
+
candidateId: 'defer-cand',
|
|
638
|
+
workspace: '/tmp/test-workspace',
|
|
639
|
+
json: true,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// Should NOT call service.intake
|
|
643
|
+
expect(mockService.intake).not.toHaveBeenCalled();
|
|
644
|
+
// Should output refused status
|
|
645
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
646
|
+
expect.stringContaining('"status": "refused"')
|
|
647
|
+
);
|
|
648
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
649
|
+
expect.stringContaining('"admissionDecision": "deferred"')
|
|
650
|
+
);
|
|
651
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('refuses intake when confidence is below threshold', async () => {
|
|
655
|
+
const mockCandidate = {
|
|
656
|
+
candidateId: 'low-conf-cand',
|
|
657
|
+
artifactId: 'artifact-1',
|
|
658
|
+
taskId: 'task-1',
|
|
659
|
+
title: 'Low confidence candidate',
|
|
660
|
+
description: 'Should not be intakeable',
|
|
661
|
+
status: 'pending',
|
|
662
|
+
recommendationKind: 'principle',
|
|
663
|
+
confidence: 0.3,
|
|
664
|
+
};
|
|
665
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
666
|
+
|
|
667
|
+
await handleCandidateIntake({
|
|
668
|
+
candidateId: 'low-conf-cand',
|
|
669
|
+
workspace: '/tmp/test-workspace',
|
|
670
|
+
json: false,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
expect(mockService.intake).not.toHaveBeenCalled();
|
|
674
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
675
|
+
expect.stringContaining('Admission gate refused')
|
|
676
|
+
);
|
|
677
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
678
|
+
expect.stringContaining('needs_evidence')
|
|
679
|
+
);
|
|
680
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('refuses intake when confidence is null (rc-3 fail loud)', async () => {
|
|
684
|
+
const mockCandidate = {
|
|
685
|
+
candidateId: 'null-conf-cand',
|
|
686
|
+
artifactId: 'artifact-1',
|
|
687
|
+
taskId: 'task-1',
|
|
688
|
+
title: 'Null confidence candidate',
|
|
689
|
+
description: 'Should not be intakeable',
|
|
690
|
+
status: 'pending',
|
|
691
|
+
recommendationKind: 'principle',
|
|
692
|
+
confidence: null,
|
|
693
|
+
};
|
|
694
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
695
|
+
|
|
696
|
+
await handleCandidateIntake({
|
|
697
|
+
candidateId: 'null-conf-cand',
|
|
698
|
+
workspace: '/tmp/test-workspace',
|
|
699
|
+
json: true,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
expect(mockService.intake).not.toHaveBeenCalled();
|
|
703
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
704
|
+
expect.stringContaining('confidence_missing_on_candidate_record')
|
|
705
|
+
);
|
|
706
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('allows intake when candidate passes admission gate', async () => {
|
|
710
|
+
const mockCandidate = {
|
|
711
|
+
candidateId: 'admitted-cand',
|
|
712
|
+
artifactId: 'artifact-1',
|
|
713
|
+
taskId: 'task-1',
|
|
714
|
+
title: 'Admitted candidate',
|
|
715
|
+
description: 'Should be intakeable',
|
|
716
|
+
status: 'pending',
|
|
717
|
+
recommendationKind: 'principle',
|
|
718
|
+
confidence: 0.8,
|
|
719
|
+
};
|
|
720
|
+
mockStateManager.getCandidate.mockResolvedValue(mockCandidate);
|
|
721
|
+
mockService.intake.mockResolvedValue({ id: 'entry-1', title: 'Test', text: 'Test', status: 'probation' });
|
|
722
|
+
|
|
723
|
+
await handleCandidateIntake({
|
|
724
|
+
candidateId: 'admitted-cand',
|
|
725
|
+
workspace: '/tmp/test-workspace',
|
|
726
|
+
json: true,
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
expect(mockService.intake).toHaveBeenCalledWith('admitted-cand');
|
|
730
|
+
});
|
|
731
|
+
});
|
|
587
732
|
});
|
|
@@ -477,6 +477,36 @@ describe('Commander wiring for backfill --include-pending', () => {
|
|
|
477
477
|
expect(output.details.totalPending).toBe(1);
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
+
// PRI-442 Stage 4: admission gate bypass prevention for backfill --include-pending
|
|
481
|
+
it('--include-pending refuses deferred candidate (admission gate bypass prevention)', async () => {
|
|
482
|
+
mockPrepareAll.mockImplementation((sql: string) => {
|
|
483
|
+
if (sql.includes("'consumed'")) return [];
|
|
484
|
+
if (sql.includes("'pending'")) return [{ candidate_id: 'cand-defer-1' }];
|
|
485
|
+
return [];
|
|
486
|
+
});
|
|
487
|
+
mockGetCandidate.mockResolvedValue({
|
|
488
|
+
candidateId: 'cand-defer-1',
|
|
489
|
+
taskId: 'diag-task-cand-defer-1',
|
|
490
|
+
description: 'deferred candidate',
|
|
491
|
+
recommendationKind: 'defer',
|
|
492
|
+
confidence: 0.9,
|
|
493
|
+
sourceRecommendationJson: JSON.stringify({ kind: 'defer', description: 'deferred' }),
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
const program = createBackfillTestProgram();
|
|
497
|
+
await program.parseAsync(['node', 'pd', 'candidate', 'internalization', 'backfill', '--workspace', WS, '--include-pending', '--confirm', '--json']);
|
|
498
|
+
|
|
499
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
|
500
|
+
// Should NOT have called intake for the deferred candidate
|
|
501
|
+
expect(mockIntake).not.toHaveBeenCalled();
|
|
502
|
+
// Should have an intake_failed result with admission gate reason
|
|
503
|
+
const failedResult = output.details.results.find(
|
|
504
|
+
(r: { status: string; reason: string }) => r.status === 'intake_failed' && r.reason.includes('Admission gate refused'),
|
|
505
|
+
);
|
|
506
|
+
expect(failedResult).toBeDefined();
|
|
507
|
+
expect(output.details.intakeFailed).toBe(1);
|
|
508
|
+
});
|
|
509
|
+
|
|
480
510
|
it('--dry-run -> RuntimeStateManager readonly=true', async () => {
|
|
481
511
|
const program = createBackfillTestProgram();
|
|
482
512
|
await program.parseAsync(['node', 'pd', 'candidate', 'internalization', 'backfill', '--workspace', WS, '--dry-run', '--json']);
|
|
@@ -86,7 +86,6 @@ describe('PRI-455: pd runtime --help shows only MVP owner subcommands', () => {
|
|
|
86
86
|
'canary',
|
|
87
87
|
'synthetic',
|
|
88
88
|
'uat',
|
|
89
|
-
'internalization',
|
|
90
89
|
'recovery',
|
|
91
90
|
'pruning',
|
|
92
91
|
'diagnostics',
|
|
@@ -133,3 +132,18 @@ describe('PRI-455: hidden commands still function (de-surface, not delete)', ()
|
|
|
133
132
|
expect(output).toContain('--apply');
|
|
134
133
|
});
|
|
135
134
|
});
|
|
135
|
+
|
|
136
|
+
describe('pd demo first-principle alias (Fix-24)', () => {
|
|
137
|
+
// cli-7-test-wiring: verify the alias resolves to the same command as story-a.
|
|
138
|
+
it('pd demo first-principle --help works and matches story-a', () => {
|
|
139
|
+
const aliasOutput = runPdHelp(['demo', 'first-principle', '--help']);
|
|
140
|
+
const storyAOutput = runPdHelp(['demo', 'story-a', '--help']);
|
|
141
|
+
|
|
142
|
+
// Both should show the same options
|
|
143
|
+
expect(aliasOutput).toContain('--workspace');
|
|
144
|
+
expect(aliasOutput).toContain('--json');
|
|
145
|
+
expect(aliasOutput).toContain('--channels');
|
|
146
|
+
// Alias output should be identical to story-a output
|
|
147
|
+
expect(aliasOutput).toBe(storyAOutput);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -499,6 +499,9 @@ describe('CLI command wiring (pd console open)', () => {
|
|
|
499
499
|
// Fake a console install: create dir + dist/server.js with a minimal HTTP server
|
|
500
500
|
const consoleDir = path.join(os.homedir(), '.openclaw', 'extensions', 'principles-disciple', 'console');
|
|
501
501
|
fs.mkdirSync(path.join(consoleDir, 'dist'), { recursive: true });
|
|
502
|
+
// EP-06 regression guard: dist/web/index.html must exist (PR #1169 fix)
|
|
503
|
+
fs.mkdirSync(path.join(consoleDir, 'dist', 'web'), { recursive: true });
|
|
504
|
+
fs.writeFileSync(path.join(consoleDir, 'dist', 'web', 'index.html'), '<!DOCTYPE html><html></html>');
|
|
502
505
|
fs.writeFileSync(path.join(consoleDir, 'dist', 'server.js'), `
|
|
503
506
|
const http = require('http');
|
|
504
507
|
const args = process.argv.slice(2);
|
|
@@ -759,6 +762,22 @@ describe('CLI command wiring (pd console open)', () => {
|
|
|
759
762
|
expect(parsed.host).toBe('::1');
|
|
760
763
|
}, 10_000);
|
|
761
764
|
});
|
|
765
|
+
|
|
766
|
+
describe('EP-06 regression guard: dist/web/index.html check (PR #1169)', () => {
|
|
767
|
+
it('returns console_web_ui_missing when dist/web/index.html does not exist', () => {
|
|
768
|
+
// beforeEach creates the fake console dir WITH dist/web/index.html.
|
|
769
|
+
// Remove it to simulate a corrupted install (the PR #1169 regression).
|
|
770
|
+
const consoleDir = path.join(os.homedir(), '.openclaw', 'extensions', 'principles-disciple', 'console');
|
|
771
|
+
const webIndex = path.join(consoleDir, 'dist', 'web', 'index.html');
|
|
772
|
+
fs.rmSync(webIndex, { force: true });
|
|
773
|
+
|
|
774
|
+
const out = runPd(['console', 'open', '--workspace', tmp, '--json', '--no-browser'], workspaceRoot, 5_000);
|
|
775
|
+
const parsed = JSON.parse(out);
|
|
776
|
+
expect(parsed.status).toBe('failed');
|
|
777
|
+
expect(parsed.reason).toBe('console_web_ui_missing');
|
|
778
|
+
expect(parsed.nextAction).toMatch(/create-principles-disciple/);
|
|
779
|
+
}, 10_000);
|
|
780
|
+
});
|
|
762
781
|
});
|
|
763
782
|
|
|
764
783
|
function runPd(args: string[], cwd: string, timeoutMs?: number): string {
|