@principles/pd-cli 1.147.10 → 1.147.12
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/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +16 -2
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-recovery-failed-tasks.d.ts.map +1 -1
- package/dist/commands/runtime-recovery-failed-tasks.js +82 -6
- package/dist/commands/runtime-recovery-failed-tasks.js.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/runtime-internalization-run-once.ts +16 -2
- package/src/commands/runtime-recovery-failed-tasks.ts +83 -6
- package/src/index.ts +6 -3
- package/tests/commands/cli-help-snapshot.test.ts +7 -1
- package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +550 -0
- package/tests/commands/runtime-internalization-run-once.test.ts +21 -2
- package/tests/commands/runtime-recovery-failed-tasks.test.ts +136 -9
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createRecoverySweepService,
|
|
4
|
+
isPeerRunnerKind,
|
|
5
|
+
isDiagnosticianStageKind,
|
|
6
|
+
type RecoverySweepServiceHandle,
|
|
7
|
+
} from '@principles/core/runtime-v2';
|
|
3
8
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
4
9
|
|
|
5
10
|
interface RecoveryFailedTasksOptions {
|
|
@@ -21,6 +26,50 @@ interface TaskDetail {
|
|
|
21
26
|
nextAction: string;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
/** Stage-task ID convention prefix → the remainder is the parent task ID. */
|
|
30
|
+
const DIAG_STAGE_ID_PREFIXES = ['diag_rootcause-', 'diag_distiller-', 'diag_router-'] as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* PRI-674 review P2: per-kind execution guidance must point at commands that
|
|
34
|
+
* actually support the recovered task kind (cli-6-output-next-action).
|
|
35
|
+
*
|
|
36
|
+
* Verified against real command surfaces:
|
|
37
|
+
* - `pd runtime internalization run-once` supports ONLY the 6 peer runner
|
|
38
|
+
* kinds (SUPPORTED_RUNNERS in runtime-internalization-run-once.ts).
|
|
39
|
+
* - `pd diagnose run --task-id <id>` is the only diagnostician execution
|
|
40
|
+
* entry (SplitDiagnosticianRunner runs the full A→B→C pipeline from the
|
|
41
|
+
* PARENT task; running a diag_* stage as parent would create bogus nested
|
|
42
|
+
* stage tasks).
|
|
43
|
+
* - diag_* stage linkage: persisted inputRef = parent task ID
|
|
44
|
+
* (SplitDiagnosticianRunner.ensureSubTask), with the
|
|
45
|
+
* diag_<stage>-<parentTaskId> ID convention as fallback. When the parent
|
|
46
|
+
* cannot be resolved we say so instead of printing a guessed command.
|
|
47
|
+
*/
|
|
48
|
+
function buildExecutionNextAction(t: { taskId: string; taskKind: string; inputRef: string | null }): string {
|
|
49
|
+
// Branch on the authoritative taskKind, never on taskId shape.
|
|
50
|
+
if (isDiagnosticianStageKind(t.taskKind)) {
|
|
51
|
+
// Parent from persisted inputRef; fallback: diag_<stage>-<parentTaskId>
|
|
52
|
+
// ID convention (plain prefix split — no regex needed for fixed literals).
|
|
53
|
+
const matchedPrefix = DIAG_STAGE_ID_PREFIXES.find((p) => t.taskId.startsWith(p));
|
|
54
|
+
const parentTaskId = t.inputRef && t.inputRef.trim() !== ''
|
|
55
|
+
? t.inputRef
|
|
56
|
+
: matchedPrefix
|
|
57
|
+
? t.taskId.slice(matchedPrefix.length).trim() || undefined
|
|
58
|
+
: undefined;
|
|
59
|
+
if (parentTaskId) {
|
|
60
|
+
return `Stage task of diagnostician parent "${parentTaskId}". Recover the parent, then run: pd diagnose run --task-id ${parentTaskId} (runs the full pipeline including this stage)`;
|
|
61
|
+
}
|
|
62
|
+
return `Stage task whose diagnostician parent could not be resolved (no inputRef linkage, task ID does not follow diag_<stage>-<parentTaskId>). Inspect: pd diagnose status --task-id ${t.taskId} — do not run a stage task directly as a parent`;
|
|
63
|
+
}
|
|
64
|
+
if (t.taskKind === 'diagnostician') {
|
|
65
|
+
return `Run: pd diagnose run --task-id ${t.taskId} (executes the full diag_rootcause→diag_distiller→diag_router pipeline)`;
|
|
66
|
+
}
|
|
67
|
+
if (isPeerRunnerKind(t.taskKind)) {
|
|
68
|
+
return `Run: pd runtime internalization run-once --runner ${t.taskKind} (executes the next ready ${t.taskKind} task)`;
|
|
69
|
+
}
|
|
70
|
+
return `Task kind ${t.taskKind} has no registered CLI execution entry. Inspect: pd diagnose status --task-id ${t.taskId}, or use the Console failed-tasks page`;
|
|
71
|
+
}
|
|
72
|
+
|
|
24
73
|
export async function handleRuntimeRecoveryFailedTasks(opts: RecoveryFailedTasksOptions): Promise<void> {
|
|
25
74
|
if (opts.dryRun && opts.confirm) {
|
|
26
75
|
if (opts.json) {
|
|
@@ -78,7 +127,7 @@ export async function handleRuntimeRecoveryFailedTasks(opts: RecoveryFailedTasks
|
|
|
78
127
|
reason: t.isExhausted
|
|
79
128
|
? `Task exhausted max attempts (${t.attemptCount}/${t.maxAttempts}) but --force specified — reset to pending`
|
|
80
129
|
: `Task failed and attempts remain (${t.attemptCount}/${t.maxAttempts}) — reset to pending`,
|
|
81
|
-
nextAction:
|
|
130
|
+
nextAction: `Task recovered to pending. ${buildExecutionNextAction({ taskId: t.taskId, taskKind: t.taskKind, inputRef: t.inputRef })}`,
|
|
82
131
|
});
|
|
83
132
|
} else {
|
|
84
133
|
skippedCount++;
|
|
@@ -117,7 +166,7 @@ export async function handleRuntimeRecoveryFailedTasks(opts: RecoveryFailedTasks
|
|
|
117
166
|
|
|
118
167
|
if (isDryRun) {
|
|
119
168
|
if (taskDetails.length === 0) {
|
|
120
|
-
summaryReason = 'No failed internalization tasks found';
|
|
169
|
+
summaryReason = 'No failed internalization or diagnostician tasks found';
|
|
121
170
|
summaryNextAction = 'Nothing to recover';
|
|
122
171
|
} else {
|
|
123
172
|
summaryReason = `Found ${recoveredCount} recoverable and ${skippedCount} exhausted failed tasks`;
|
|
@@ -125,9 +174,37 @@ export async function handleRuntimeRecoveryFailedTasks(opts: RecoveryFailedTasks
|
|
|
125
174
|
}
|
|
126
175
|
} else {
|
|
127
176
|
summaryReason = `Successfully recovered ${recoveredCount} failed tasks, skipped ${skippedCount} tasks`;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
// PRI-674 review P2: mixed recoveries span different executors — split
|
|
178
|
+
// the summary by actual command surface instead of one command for all.
|
|
179
|
+
if (recoveredCount > 0) {
|
|
180
|
+
const diagParents: string[] = [];
|
|
181
|
+
const diagStages: string[] = [];
|
|
182
|
+
const peerTasks: string[] = [];
|
|
183
|
+
const otherTasks: string[] = [];
|
|
184
|
+
for (const t of taskDetails) {
|
|
185
|
+
if (t.action !== 'recovered') continue;
|
|
186
|
+
if (t.taskKind === 'diagnostician') diagParents.push(t.taskId);
|
|
187
|
+
else if (isDiagnosticianStageKind(t.taskKind)) diagStages.push(t.taskId);
|
|
188
|
+
else if (isPeerRunnerKind(t.taskKind)) peerTasks.push(t.taskId);
|
|
189
|
+
else otherTasks.push(t.taskId);
|
|
190
|
+
}
|
|
191
|
+
const lines: string[] = [];
|
|
192
|
+
if (diagParents.length > 0) {
|
|
193
|
+
lines.push(`For diagnostician parent task(s) (${diagParents.join(', ')}): pd diagnose run --task-id <taskId>`);
|
|
194
|
+
}
|
|
195
|
+
if (diagStages.length > 0) {
|
|
196
|
+
lines.push(`For diag_* stage task(s) (${diagStages.join(', ')}): recover the parent diagnostician task, then pd diagnose run --task-id <parentTaskId> (each stage's parent is in its per-task nextAction)`);
|
|
197
|
+
}
|
|
198
|
+
if (peerTasks.length > 0) {
|
|
199
|
+
lines.push(`For internalization task(s) (${peerTasks.join(', ')}): pd runtime internalization run-once --runner <kind>`);
|
|
200
|
+
}
|
|
201
|
+
if (otherTasks.length > 0) {
|
|
202
|
+
lines.push(`For other task(s) (${otherTasks.join(', ')}): no registered CLI execution entry — see per-task nextAction`);
|
|
203
|
+
}
|
|
204
|
+
summaryNextAction = `Tasks recovered to pending. Execute by kind: ${lines.join('; ')}`;
|
|
205
|
+
} else {
|
|
206
|
+
summaryNextAction = 'No tasks recovered';
|
|
207
|
+
}
|
|
131
208
|
}
|
|
132
209
|
|
|
133
210
|
if (opts.json) {
|
package/src/index.ts
CHANGED
|
@@ -870,9 +870,12 @@ const diagnosticsCmd = runtimeCmd
|
|
|
870
870
|
.command('diagnostics', { hidden: true })
|
|
871
871
|
.description('Control plane diagnostic bundle operations');
|
|
872
872
|
|
|
873
|
+
// PRI-674: visible (was hidden) — the only recovery re-entry point for an
|
|
874
|
+
// Owner without Console access; hidden-ness left failed diagnostician tasks
|
|
875
|
+
// undiscoverable.
|
|
873
876
|
const recoveryCmd = runtimeCmd
|
|
874
|
-
.command('recovery'
|
|
875
|
-
.description('Runtime V2 lease recovery
|
|
877
|
+
.command('recovery')
|
|
878
|
+
.description('Runtime V2 recovery operations (expired-lease sweep, failed-task recovery)');
|
|
876
879
|
|
|
877
880
|
// PRI-555 phase 1: dry-run-only artifact identity drift repair planner.
|
|
878
881
|
runtimeCmd
|
|
@@ -907,7 +910,7 @@ recoveryCmd
|
|
|
907
910
|
|
|
908
911
|
recoveryCmd
|
|
909
912
|
.command('failed-tasks')
|
|
910
|
-
.description('Recover failed internalization tasks')
|
|
913
|
+
.description('Recover failed internalization and diagnostician tasks')
|
|
911
914
|
.option('-w, --workspace <path>', 'Workspace directory')
|
|
912
915
|
.option('--dry-run', 'Report only, no modifications (default)')
|
|
913
916
|
.option('--confirm', 'Actually recover failed tasks')
|
|
@@ -81,12 +81,18 @@ describe('PRI-455: pd runtime --help shows only MVP owner subcommands', () => {
|
|
|
81
81
|
expect(runtimeHelp).toMatch(/\bfeatures\b/);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
// PRI-674: recovery is now owner-facing — it is the only failed-task
|
|
85
|
+
// recovery re-entry point for an Owner without Console access, so hiding
|
|
86
|
+
// it made failed diagnostician tasks undiscoverable.
|
|
87
|
+
it('runtime --help shows recovery', () => {
|
|
88
|
+
expect(runtimeHelp).toMatch(/^\s+recovery\b/m);
|
|
89
|
+
});
|
|
90
|
+
|
|
84
91
|
// Operator subcommands that should be hidden
|
|
85
92
|
const HIDDEN_RUNTIME_SUBCOMMANDS = [
|
|
86
93
|
'canary',
|
|
87
94
|
'synthetic',
|
|
88
95
|
'uat',
|
|
89
|
-
'recovery',
|
|
90
96
|
'pruning',
|
|
91
97
|
'diagnostics',
|
|
92
98
|
'probe',
|