@principles/pd-cli 1.93.0 → 1.95.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/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +18 -16
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +14 -3
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +24 -16
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/services/console-launcher.js +1 -1
- package/dist/services/console-launcher.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +95 -10
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +97 -6
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/diagnose.ts +32 -18
- package/src/commands/pain-record.ts +15 -3
- package/src/commands/pain-retry.ts +41 -18
- package/src/services/console-launcher.ts +1 -1
- package/src/services/pain-flood-simulation-runner.ts +109 -10
- package/src/services/synthetic-baseline-runner.ts +111 -9
- package/tests/commands/console-open.test.ts +9 -0
- package/tests/commands/diagnose.test.ts +14 -4
- package/tests/commands/pain-record-async.test.ts +76 -1
- package/tests/commands/pain-retry.test.ts +13 -2
- package/tests/e2e/candidate-intake-e2e.test.ts +25 -4
|
@@ -4,8 +4,14 @@ import { RuntimeStateManager } from '@principles/core/runtime-v2';
|
|
|
4
4
|
import { SqliteContextAssembler } from '@principles/core/runtime-v2';
|
|
5
5
|
import { SqliteHistoryQuery } from '@principles/core/runtime-v2';
|
|
6
6
|
import { StoreEventEmitter } from '@principles/core/runtime-v2';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
SplitDiagnosticianRunner,
|
|
9
|
+
DiagRootCauseRunner,
|
|
10
|
+
DiagDistillerRunner,
|
|
11
|
+
DiagRouterRunner,
|
|
12
|
+
DefaultDiagRootCauseValidator,
|
|
13
|
+
DefaultDiagDistillerValidator,
|
|
14
|
+
} from '@principles/core/runtime-v2';
|
|
9
15
|
import { SqliteDiagnosticianCommitter } from '@principles/core/runtime-v2';
|
|
10
16
|
import { TestDoubleRuntimeAdapter } from '@principles/core/runtime-v2';
|
|
11
17
|
import { PainSignalBridge } from '@principles/core/runtime-v2';
|
|
@@ -72,20 +78,107 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
|
|
|
72
78
|
const contextAssembler = new SqliteContextAssembler(taskStore, historyQuery, runStore);
|
|
73
79
|
const eventEmitter = new StoreEventEmitter();
|
|
74
80
|
const committer = new SqliteDiagnosticianCommitter(sqliteConn);
|
|
75
|
-
const
|
|
81
|
+
const runIdToTaskId = new Map<string, string>();
|
|
82
|
+
let runCounter = 0;
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
// Single test-double adapter handles all pains with stage-aware output
|
|
85
|
+
const runtimeAdapter = new TestDoubleRuntimeAdapter({
|
|
86
|
+
onStartRun: (input) => {
|
|
87
|
+
runCounter += 1;
|
|
88
|
+
const runId = `td-${runCounter}`;
|
|
89
|
+
const taskId = input.taskRef?.taskId ?? '';
|
|
90
|
+
runIdToTaskId.set(runId, taskId);
|
|
91
|
+
return {
|
|
92
|
+
runId,
|
|
93
|
+
runtimeKind: 'test-double',
|
|
94
|
+
startedAt: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
onFetchOutput: async (runId: string) => {
|
|
98
|
+
const taskId = runIdToTaskId.get(runId) ?? '';
|
|
99
|
+
if (taskId.includes('diag_rootcause')) {
|
|
100
|
+
return {
|
|
101
|
+
runId,
|
|
102
|
+
payload: {
|
|
103
|
+
valid: true,
|
|
104
|
+
diagnosisId: `diag-${runId}`,
|
|
105
|
+
taskId,
|
|
106
|
+
summary: 'Mock rootcause summary for synthetic-baseline',
|
|
107
|
+
causalChain: [{ why: 1, statement: 'why', evidenceRefs: ['ref-1'] }],
|
|
108
|
+
rootCause: 'Design: Mock rootcause',
|
|
109
|
+
rootCauseCategory: 'Design',
|
|
110
|
+
evidence: [{ sourceRef: 'ref-1', note: 'note' }],
|
|
111
|
+
confidence: 0.9,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
} else if (taskId.includes('diag_distiller')) {
|
|
115
|
+
const parentTaskId = taskId.replace('diag_distiller-', '');
|
|
116
|
+
const stageATaskId = `diag_rootcause-${parentTaskId}`;
|
|
117
|
+
const artifacts = stateManager
|
|
118
|
+
? await stateManager.piArtifactStore.listBySourceTaskId(stageATaskId)
|
|
119
|
+
: [];
|
|
120
|
+
const sourceRootCauseArtifactId = artifacts[0]?.artifactId ?? 'art-rc';
|
|
121
|
+
return {
|
|
122
|
+
runId,
|
|
123
|
+
payload: {
|
|
124
|
+
valid: true,
|
|
125
|
+
taskId,
|
|
126
|
+
sourceRootCauseArtifactId,
|
|
127
|
+
abstractedPrinciple: 'Mock abstracted principle',
|
|
128
|
+
rationale: 'Mock rationale',
|
|
129
|
+
groundedOnCorePrincipleIds: ['T-01'],
|
|
130
|
+
scope: 'domain',
|
|
131
|
+
confidence: 0.9,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
} else {
|
|
135
|
+
return {
|
|
136
|
+
runId,
|
|
137
|
+
payload: diagnosticianOutput,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
81
142
|
|
|
82
|
-
const
|
|
143
|
+
const rootCauseRunner = new DiagRootCauseRunner(
|
|
83
144
|
{
|
|
84
145
|
stateManager,
|
|
146
|
+
runtimeAdapter,
|
|
147
|
+
eventEmitter,
|
|
148
|
+
artifactStore: stateManager.piArtifactStore,
|
|
149
|
+
validator: new DefaultDiagRootCauseValidator(),
|
|
85
150
|
contextAssembler,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
owner: 'synthetic-baseline',
|
|
154
|
+
runtimeKind: 'test-double',
|
|
155
|
+
pollIntervalMs: 50,
|
|
156
|
+
timeoutMs: 10000,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const distillerRunner = new DiagDistillerRunner(
|
|
161
|
+
{
|
|
162
|
+
stateManager,
|
|
86
163
|
runtimeAdapter,
|
|
87
164
|
eventEmitter,
|
|
88
|
-
|
|
165
|
+
artifactStore: stateManager.piArtifactStore,
|
|
166
|
+
validator: new DefaultDiagDistillerValidator(),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
owner: 'synthetic-baseline',
|
|
170
|
+
runtimeKind: 'test-double',
|
|
171
|
+
pollIntervalMs: 50,
|
|
172
|
+
timeoutMs: 10000,
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const routerRunner = new DiagRouterRunner(
|
|
177
|
+
{
|
|
178
|
+
stateManager,
|
|
179
|
+
runtimeAdapter,
|
|
180
|
+
eventEmitter,
|
|
181
|
+
artifactStore: stateManager.piArtifactStore,
|
|
89
182
|
committer,
|
|
90
183
|
},
|
|
91
184
|
{
|
|
@@ -96,6 +189,15 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
|
|
|
96
189
|
},
|
|
97
190
|
);
|
|
98
191
|
|
|
192
|
+
const runner = new SplitDiagnosticianRunner({
|
|
193
|
+
rootCauseRunner,
|
|
194
|
+
distillerRunner,
|
|
195
|
+
routerRunner,
|
|
196
|
+
stateManager,
|
|
197
|
+
committer,
|
|
198
|
+
perStageTimeoutMs: 10000,
|
|
199
|
+
});
|
|
200
|
+
|
|
99
201
|
const ledgerAdapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
100
202
|
const intakeService = new CandidateIntakeService({ stateManager, ledgerAdapter });
|
|
101
203
|
|
|
@@ -758,9 +758,18 @@ describe('CLI command wiring (pd console open)', () => {
|
|
|
758
758
|
|
|
759
759
|
function runPd(args: string[], cwd: string): string {
|
|
760
760
|
try {
|
|
761
|
+
const env: Record<string, string> = { ...process.env };
|
|
762
|
+
if (!args.includes('--workspace') && !args.includes('--help') && !args.includes('-h')) {
|
|
763
|
+
env.USERPROFILE = '/nonexistent';
|
|
764
|
+
env.HOME = '/nonexistent';
|
|
765
|
+
env.HOMEPATH = '/nonexistent';
|
|
766
|
+
env.HOMEDRIVE = '/nonexistent';
|
|
767
|
+
delete env.PD_WORKSPACE_DIR;
|
|
768
|
+
}
|
|
761
769
|
return execFileSync('node', [getBuiltPdCliPath(), ...args], {
|
|
762
770
|
encoding: 'utf8',
|
|
763
771
|
cwd,
|
|
772
|
+
env,
|
|
764
773
|
});
|
|
765
774
|
} catch (err: unknown) {
|
|
766
775
|
if (err && typeof err === 'object' && Object.hasOwn(err, 'stdout')) {
|
|
@@ -57,11 +57,17 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
57
57
|
SqliteSourceTraceLocator: vi.fn().mockImplementation(function () { return {}; }),
|
|
58
58
|
StoreEventEmitter: vi.fn().mockImplementation(function () { return {}; }),
|
|
59
59
|
storeEmitter: { emitTelemetry: vi.fn() },
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
SplitDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
61
|
+
DiagRootCauseRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
62
|
+
DiagDistillerRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
63
|
+
DiagRouterRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
64
|
+
DefaultDiagRootCauseValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
65
|
+
DefaultDiagDistillerValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
66
|
+
DisabledDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
63
67
|
TestDoubleRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
64
68
|
OpenClawCliRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
69
|
+
PiAiRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
70
|
+
SPLIT_PIPELINE_TOTAL_TIMEOUT_MS: 300000,
|
|
65
71
|
PDRuntimeError: class PDRuntimeError extends Error {
|
|
66
72
|
constructor(public category: string, message: string) {
|
|
67
73
|
super(message);
|
|
@@ -78,8 +84,12 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
78
84
|
agentId: 'main',
|
|
79
85
|
}),
|
|
80
86
|
isRuntimeConfigError: vi.fn().mockReturnValue(false),
|
|
81
|
-
isFeatureEnabled: vi.fn().mockReturnValue(
|
|
87
|
+
isFeatureEnabled: vi.fn().mockReturnValue(true),
|
|
82
88
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
89
|
+
validatePdConfig: vi.fn().mockReturnValue({ valid: true, errors: [] }),
|
|
90
|
+
computeEffectivePdConfig: vi.fn().mockReturnValue({ config: {}, source: 'defaults', warnings: [] }),
|
|
91
|
+
computeFeatureFlagsFromConfig: vi.fn().mockReturnValue({}),
|
|
92
|
+
redactPdConfig: vi.fn().mockImplementation((c) => c),
|
|
83
93
|
run: vi.fn().mockResolvedValue({
|
|
84
94
|
status: 'succeeded',
|
|
85
95
|
taskId: 'test-task-1',
|
|
@@ -155,7 +155,7 @@ describe('pd pain record async mode (PRI-369)', () => {
|
|
|
155
155
|
const allOutput = logSpy.mock.calls.map(c => c.join(' ')).join(' ');
|
|
156
156
|
expect(allOutput).toContain('[SUBMITTED]');
|
|
157
157
|
expect(allOutput).toContain('submitted');
|
|
158
|
-
expect(allOutput).toContain('pd task
|
|
158
|
+
expect(allOutput).toContain('Next action: pd diagnose run --task-id');
|
|
159
159
|
expect(exitSpy).not.toHaveBeenCalledWith(1);
|
|
160
160
|
|
|
161
161
|
logSpy.mockRestore();
|
|
@@ -240,4 +240,79 @@ describe('pd pain record async mode (PRI-369)', () => {
|
|
|
240
240
|
logSpy.mockRestore();
|
|
241
241
|
exitSpy.mockRestore();
|
|
242
242
|
});
|
|
243
|
+
|
|
244
|
+
// 7. Stubbed process.exit(1) does not cause side effects in JSON mode
|
|
245
|
+
it('stubbed process.exit(1) in JSON mode does not execute else branch', async () => {
|
|
246
|
+
mockRecordPainResult = {
|
|
247
|
+
status: 'failed',
|
|
248
|
+
painId: 'manual_123_abc',
|
|
249
|
+
taskId: 'diagnosis_manual_123_abc',
|
|
250
|
+
candidateIds: ['c1'],
|
|
251
|
+
ledgerEntryIds: ['l1'],
|
|
252
|
+
observabilityWarnings: [],
|
|
253
|
+
failureCategory: 'runtime_unavailable' as FailureCategory,
|
|
254
|
+
latencyMs: 5,
|
|
255
|
+
message: 'Task creation failed',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
259
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
260
|
+
const exitSpy = mockProcessExit();
|
|
261
|
+
|
|
262
|
+
await handlePainRecord({ reason: 'test pain', json: true });
|
|
263
|
+
|
|
264
|
+
// Verify JSON output was printed
|
|
265
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
266
|
+
const jsonOutput = JSON.parse(logSpy.mock.calls[0][0]);
|
|
267
|
+
expect(jsonOutput.status).toBe('failed');
|
|
268
|
+
expect(jsonOutput.candidateIds).toEqual(['c1']);
|
|
269
|
+
|
|
270
|
+
// Verify process.exit(1) was called
|
|
271
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
272
|
+
|
|
273
|
+
// Verify else branch was NOT executed (no error.log for [FAIL] message)
|
|
274
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
275
|
+
|
|
276
|
+
logSpy.mockRestore();
|
|
277
|
+
errorSpy.mockRestore();
|
|
278
|
+
exitSpy.mockRestore();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// 8. Stubbed process.exit(1) does not cause side effects in text mode
|
|
282
|
+
it('stubbed process.exit(1) in text mode stops after error message', async () => {
|
|
283
|
+
mockRecordPainResult = {
|
|
284
|
+
status: 'failed',
|
|
285
|
+
painId: 'manual_123_abc',
|
|
286
|
+
taskId: 'diagnosis_manual_123_abc',
|
|
287
|
+
candidateIds: ['c1'],
|
|
288
|
+
ledgerEntryIds: ['l1'],
|
|
289
|
+
observabilityWarnings: [],
|
|
290
|
+
failureCategory: 'runtime_unavailable' as FailureCategory,
|
|
291
|
+
latencyMs: 5,
|
|
292
|
+
message: 'Test failure message',
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
296
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
297
|
+
const exitSpy = mockProcessExit();
|
|
298
|
+
|
|
299
|
+
await handlePainRecord({ reason: 'test pain' }); // Not json mode
|
|
300
|
+
|
|
301
|
+
// Verify [FAIL] error message was printed
|
|
302
|
+
expect(errorSpy).toHaveBeenCalledWith('[FAIL] Pain signal failed:', 'Test failure message');
|
|
303
|
+
|
|
304
|
+
// Verify process.exit(1) was called
|
|
305
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
306
|
+
|
|
307
|
+
// Verify no success/submitted/skip/retry messages were printed after exit
|
|
308
|
+
const allLogMessages = logSpy.mock.calls.flat();
|
|
309
|
+
const forbiddenPatterns = ['[OK]', '[SUBMITTED]', '[SKIP]', '[RETRY]'];
|
|
310
|
+
for (const pattern of forbiddenPatterns) {
|
|
311
|
+
expect(allLogMessages).not.toEqual(expect.stringContaining(pattern));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
logSpy.mockRestore();
|
|
315
|
+
errorSpy.mockRestore();
|
|
316
|
+
exitSpy.mockRestore();
|
|
317
|
+
});
|
|
243
318
|
});
|
|
@@ -89,11 +89,17 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
89
89
|
SqliteSourceTraceLocator: vi.fn().mockImplementation(function () { return {}; }),
|
|
90
90
|
StoreEventEmitter: vi.fn().mockImplementation(function () { return {}; }),
|
|
91
91
|
storeEmitter: { emitTelemetry: vi.fn() },
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
SplitDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
93
|
+
DiagRootCauseRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
94
|
+
DiagDistillerRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
95
|
+
DiagRouterRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
96
|
+
DefaultDiagRootCauseValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
97
|
+
DefaultDiagDistillerValidator: vi.fn().mockImplementation(function () { return {}; }),
|
|
98
|
+
DisabledDiagnosticianRunner: vi.fn().mockImplementation(function () { return {}; }),
|
|
94
99
|
TestDoubleRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
95
100
|
OpenClawCliRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
96
101
|
PiAiRuntimeAdapter: vi.fn().mockImplementation(function () { return {}; }),
|
|
102
|
+
SPLIT_PIPELINE_TOTAL_TIMEOUT_MS: 300000,
|
|
97
103
|
PDRuntimeError: class PDRuntimeError extends Error {
|
|
98
104
|
constructor(public category: string, message: string) {
|
|
99
105
|
super(message);
|
|
@@ -103,7 +109,12 @@ vi.mock('@principles/core/runtime-v2', () => {
|
|
|
103
109
|
CandidateIntakeService: MockCandidateIntakeService,
|
|
104
110
|
resolveRuntimeConfig: mockResolveRuntimeConfig,
|
|
105
111
|
isRuntimeConfigError: vi.fn().mockReturnValue(false),
|
|
112
|
+
isFeatureEnabled: vi.fn().mockReturnValue(true),
|
|
106
113
|
resolveOutputLanguage: vi.fn().mockReturnValue({ outputLanguage: 'zh-CN' }),
|
|
114
|
+
validatePdConfig: vi.fn().mockReturnValue({ valid: true, errors: [] }),
|
|
115
|
+
computeEffectivePdConfig: vi.fn().mockReturnValue({ config: {}, source: 'defaults', warnings: [] }),
|
|
116
|
+
computeFeatureFlagsFromConfig: vi.fn().mockReturnValue({}),
|
|
117
|
+
redactPdConfig: vi.fn().mockImplementation((c) => c),
|
|
107
118
|
run: mockRun,
|
|
108
119
|
status: vi.fn(),
|
|
109
120
|
};
|
|
@@ -149,12 +149,33 @@ describe('E2E: pd candidate intake flow', () => {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function readLedgerFile(workspace: string):
|
|
153
|
-
const ledgerPath = join(workspace, '.
|
|
152
|
+
function readLedgerFile(workspace: string): Array<{ id: string; sourceRef: string; status: string; evaluability: string }> {
|
|
153
|
+
const ledgerPath = join(workspace, '.state', 'principle_training_state.json');
|
|
154
154
|
if (!existsSync(ledgerPath)) return [];
|
|
155
155
|
const content = readFileSync(ledgerPath, 'utf-8');
|
|
156
|
-
const data = JSON.parse(content);
|
|
157
|
-
|
|
156
|
+
const data: unknown = JSON.parse(content);
|
|
157
|
+
if (typeof data !== 'object' || data === null) return [];
|
|
158
|
+
const tree = (data as Record<string, unknown>)._tree ?? (data as Record<string, unknown>).tree ?? {};
|
|
159
|
+
if (typeof tree !== 'object' || tree === null) return [];
|
|
160
|
+
const principles = (tree as Record<string, unknown>).principles ?? {};
|
|
161
|
+
if (typeof principles !== 'object' || principles === null) return [];
|
|
162
|
+
return Object.values(principles).map((p: unknown) => {
|
|
163
|
+
if (typeof p !== 'object' || p === null) {
|
|
164
|
+
return { id: '', sourceRef: 'candidate://', status: '', evaluability: '' };
|
|
165
|
+
}
|
|
166
|
+
const pObj = p as Record<string, unknown>;
|
|
167
|
+
const painIds = pObj.derivedFromPainIds;
|
|
168
|
+
const candidateId = Array.isArray(painIds) && typeof painIds[0] === 'string' ? painIds[0] : '';
|
|
169
|
+
const id = typeof pObj.id === 'string' ? pObj.id : '';
|
|
170
|
+
const rawStatus = typeof pObj.status === 'string' ? pObj.status : '';
|
|
171
|
+
const evaluability = typeof pObj.evaluability === 'string' ? pObj.evaluability : '';
|
|
172
|
+
return {
|
|
173
|
+
id,
|
|
174
|
+
sourceRef: `candidate://${candidateId}`,
|
|
175
|
+
status: rawStatus === 'candidate' ? 'probation' : rawStatus,
|
|
176
|
+
evaluability,
|
|
177
|
+
};
|
|
178
|
+
});
|
|
158
179
|
}
|
|
159
180
|
|
|
160
181
|
it('Test 1 (Happy path E2E): pending candidate → intake → consumed → ledgerEntryId in output', () => {
|