@principles/core 1.87.0 → 1.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime-v2/__tests__/architecture-regression.test.js +39 -0
- package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js +425 -2
- package/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js.map +1 -1
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts +15 -0
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts.map +1 -0
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +685 -0
- package/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -0
- package/dist/runtime-v2/config/index.d.ts +14 -0
- package/dist/runtime-v2/config/index.d.ts.map +1 -0
- package/dist/runtime-v2/config/index.js +16 -0
- package/dist/runtime-v2/config/index.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-defaults.d.ts +14 -0
- package/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-defaults.js +74 -0
- package/dist/runtime-v2/config/pd-config-defaults.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-effective.d.ts +14 -0
- package/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-effective.js +118 -0
- package/dist/runtime-v2/config/pd-config-effective.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-feature-flags.d.ts +35 -0
- package/dist/runtime-v2/config/pd-config-feature-flags.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-feature-flags.js +99 -0
- package/dist/runtime-v2/config/pd-config-feature-flags.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-redaction.d.ts +22 -0
- package/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-redaction.js +179 -0
- package/dist/runtime-v2/config/pd-config-redaction.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-types.d.ts +124 -0
- package/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-types.js +35 -0
- package/dist/runtime-v2/config/pd-config-types.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-validate.d.ts +16 -0
- package/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -0
- package/dist/runtime-v2/config/pd-config-validate.js +443 -0
- package/dist/runtime-v2/config/pd-config-validate.js.map +1 -0
- package/dist/runtime-v2/index.d.ts +2 -0
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +6 -0
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/scribe-output.d.ts +8 -2
- package/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-output.js +33 -17
- package/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.d.ts +56 -49
- package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.js +116 -398
- package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +119 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +8 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { PDRuntimeError } from '../error-categories.js';
|
|
1
|
+
import { PDRuntimeError, isPDErrorCategory } from '../error-categories.js';
|
|
2
2
|
import { hydratePITaskRecord } from './pitask-metadata.js';
|
|
3
|
-
import { RunnerPhase } from '../runner/runner-phase.js';
|
|
4
3
|
import { ScribePromptBuilder } from './scribe-prompt-builder.js';
|
|
5
4
|
import { injectRunnerLineageIfAbsent } from './peer-runner-contracts.js';
|
|
6
|
-
|
|
5
|
+
import { BasePeerRunner } from '../runner/base-peer-runner.js';
|
|
6
|
+
export const DEFAULT_SCRIBE_RUNNER_OPTIONS = {
|
|
7
7
|
pollIntervalMs: 5_000,
|
|
8
8
|
timeoutMs: 300_000,
|
|
9
9
|
defaultMaxAttempts: 3,
|
|
10
10
|
agentId: 'scribe',
|
|
11
11
|
};
|
|
12
|
-
function resolveScribeRunnerOptions(options) {
|
|
12
|
+
export function resolveScribeRunnerOptions(options) {
|
|
13
13
|
return {
|
|
14
14
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_SCRIBE_RUNNER_OPTIONS.pollIntervalMs,
|
|
15
15
|
timeoutMs: options.timeoutMs ?? DEFAULT_SCRIBE_RUNNER_OPTIONS.timeoutMs,
|
|
@@ -19,117 +19,22 @@ function resolveScribeRunnerOptions(options) {
|
|
|
19
19
|
agentId: options.agentId ?? DEFAULT_SCRIBE_RUNNER_OPTIONS.agentId,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
resolvedOptions;
|
|
25
|
-
stateManager;
|
|
26
|
-
runtimeAdapter;
|
|
27
|
-
eventEmitter;
|
|
22
|
+
// ── ScribeRunner ─────────────────────────────────────────────────────────────
|
|
23
|
+
export class ScribeRunner extends BasePeerRunner {
|
|
28
24
|
validator;
|
|
29
|
-
artifactStore;
|
|
30
25
|
constructor(deps, options) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.resolvedOptions = resolveScribeRunnerOptions(options);
|
|
37
|
-
}
|
|
38
|
-
get currentPhase() {
|
|
39
|
-
return this.phase;
|
|
40
|
-
}
|
|
41
|
-
emitScribeEvent(eventType, taskId, payload) {
|
|
42
|
-
this.eventEmitter.emitTelemetry({
|
|
43
|
-
eventType: eventType,
|
|
44
|
-
traceId: taskId,
|
|
45
|
-
timestamp: new Date().toISOString(),
|
|
46
|
-
sessionId: this.resolvedOptions.owner,
|
|
47
|
-
agentId: this.resolvedOptions.agentId,
|
|
48
|
-
payload,
|
|
26
|
+
super(deps, options, {
|
|
27
|
+
runnerName: 'scribe',
|
|
28
|
+
expectedTaskKind: 'scribe',
|
|
29
|
+
defaultAgentId: 'scribe',
|
|
30
|
+
resultRefPrefix: 'scribe',
|
|
49
31
|
});
|
|
32
|
+
this.validator = deps.validator;
|
|
50
33
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
leasedTask = await this.stateManager.acquireLease({
|
|
56
|
-
taskId,
|
|
57
|
-
owner: this.resolvedOptions.owner,
|
|
58
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
return await this.handleLeaseOrPhaseError(taskId, error);
|
|
63
|
-
}
|
|
64
|
-
if (leasedTask.taskKind !== 'scribe') {
|
|
65
|
-
this.emitScribeEvent('scribe_wrong_task_kind', taskId, {
|
|
66
|
-
expectedKind: 'scribe',
|
|
67
|
-
actualKind: leasedTask.taskKind,
|
|
68
|
-
});
|
|
69
|
-
await this.stateManager.markTaskFailed(taskId, 'input_invalid');
|
|
70
|
-
return {
|
|
71
|
-
status: 'failed',
|
|
72
|
-
taskId,
|
|
73
|
-
errorCategory: 'input_invalid',
|
|
74
|
-
failureReason: `Task kind must be 'scribe', got '${leasedTask.taskKind}'`,
|
|
75
|
-
attemptCount: leasedTask.attemptCount,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
this.emitScribeEvent('scribe_task_leased', taskId, {
|
|
79
|
-
taskKind: 'scribe',
|
|
80
|
-
attemptCount: leasedTask.attemptCount,
|
|
81
|
-
});
|
|
82
|
-
try {
|
|
83
|
-
const storeRunId = await this.resolveStoreRunId(taskId);
|
|
84
|
-
this.phase = RunnerPhase.BuildingContext;
|
|
85
|
-
const { contextHash, philosopherArtifact, sourcePhilosopherArtifactId } = await this.buildContext(taskId);
|
|
86
|
-
if (!philosopherArtifact) {
|
|
87
|
-
return this.retryOrFail({
|
|
88
|
-
taskId,
|
|
89
|
-
task: leasedTask,
|
|
90
|
-
errorCategory: 'input_invalid',
|
|
91
|
-
failureReason: 'Philosopher dependency artifact not found',
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
this.emitScribeEvent('scribe_context_built', taskId, { contextHash });
|
|
95
|
-
this.phase = RunnerPhase.Invoking;
|
|
96
|
-
const runHandle = await this.invokeRuntime({ taskId, contextHash, philosopherArtifact, sourcePhilosopherArtifactId });
|
|
97
|
-
this.emitScribeEvent('scribe_run_started', taskId, {
|
|
98
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
99
|
-
});
|
|
100
|
-
this.phase = RunnerPhase.Polling;
|
|
101
|
-
const finalStatus = await this.pollUntilTerminal(runHandle);
|
|
102
|
-
if (finalStatus.status !== 'succeeded') {
|
|
103
|
-
return await this.handleRuntimeFailure(taskId, leasedTask, finalStatus);
|
|
104
|
-
}
|
|
105
|
-
this.phase = RunnerPhase.FetchingOutput;
|
|
106
|
-
const output = await this.fetchAndParseOutput(runHandle.runId);
|
|
107
|
-
// Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
108
|
-
injectRunnerLineageIfAbsent(output, 'taskId', taskId);
|
|
109
|
-
this.phase = RunnerPhase.Validating;
|
|
110
|
-
const validationResult = await this.validator.validate(output, taskId, sourcePhilosopherArtifactId ?? undefined);
|
|
111
|
-
if (!validationResult.valid) {
|
|
112
|
-
return await this.handleValidationError({
|
|
113
|
-
taskId,
|
|
114
|
-
task: leasedTask,
|
|
115
|
-
errors: validationResult.errors,
|
|
116
|
-
errorCategory: validationResult.errorCategory,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
this.emitScribeEvent('scribe_output_validated', taskId, {
|
|
120
|
-
principleTitle: output.principleDraft.title,
|
|
121
|
-
});
|
|
122
|
-
return await this.succeedTask({
|
|
123
|
-
taskId,
|
|
124
|
-
runId: storeRunId,
|
|
125
|
-
output,
|
|
126
|
-
task: leasedTask,
|
|
127
|
-
contextHash,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
return await this.handlePostLeaseError(taskId, leasedTask, error);
|
|
132
|
-
}
|
|
34
|
+
// ── Abstract implementations ───────────────────────────────────────────────
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
36
|
+
get permanentErrorCategories() {
|
|
37
|
+
return new Set(['storage_unavailable', 'workspace_invalid', 'capability_missing', 'cancelled', 'input_invalid']);
|
|
133
38
|
}
|
|
134
39
|
async buildContext(taskId) {
|
|
135
40
|
const task = await this.stateManager.getTask(taskId);
|
|
@@ -139,8 +44,7 @@ export class ScribeRunner {
|
|
|
139
44
|
const piTask = hydratePITaskRecord(task);
|
|
140
45
|
const deps = piTask?.dependencyTaskIds ?? [];
|
|
141
46
|
if (deps.length === 0) {
|
|
142
|
-
|
|
143
|
-
return { contextHash: 'empty', philosopherArtifact: null, sourcePhilosopherArtifactId: null };
|
|
47
|
+
throw new PDRuntimeError('input_invalid', 'Philosopher dependency artifact not found');
|
|
144
48
|
}
|
|
145
49
|
for (const depId of deps) {
|
|
146
50
|
const depTask = await this.stateManager.getTask(depId);
|
|
@@ -149,7 +53,7 @@ export class ScribeRunner {
|
|
|
149
53
|
if (depTask.taskKind !== 'philosopher')
|
|
150
54
|
continue;
|
|
151
55
|
if (depTask.status !== 'succeeded') {
|
|
152
|
-
this.
|
|
56
|
+
this.emitEvent('dependency_not_succeeded', taskId, {
|
|
153
57
|
depTaskId: depId,
|
|
154
58
|
depStatus: depTask.status,
|
|
155
59
|
});
|
|
@@ -163,358 +67,172 @@ export class ScribeRunner {
|
|
|
163
67
|
continue;
|
|
164
68
|
const artifactRef = depPiTask?.outputArtifactRefs?.[0]?.ref ?? `pi-artifact://${depId}`;
|
|
165
69
|
return {
|
|
166
|
-
contextHash:
|
|
70
|
+
contextHash: BasePeerRunner.hashContextRefs([artifactRef]),
|
|
167
71
|
philosopherArtifact: firstArtifact.contentJson,
|
|
168
72
|
sourcePhilosopherArtifactId: firstArtifact.artifactId,
|
|
169
73
|
};
|
|
170
74
|
}
|
|
171
75
|
}
|
|
172
|
-
|
|
173
|
-
return { contextHash: 'empty', philosopherArtifact: null, sourcePhilosopherArtifactId: null };
|
|
76
|
+
throw new PDRuntimeError('input_invalid', 'Philosopher dependency artifact not found');
|
|
174
77
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let hash = 0;
|
|
180
|
-
for (let i = 0; i < str.length; i++) {
|
|
181
|
-
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
182
|
-
}
|
|
183
|
-
return `ctx-${Math.abs(hash).toString(16)}`;
|
|
184
|
-
}
|
|
185
|
-
async resolveStoreRunId(taskId) {
|
|
186
|
-
const runs = await this.stateManager.getRunsByTask(taskId);
|
|
187
|
-
const latestRun = runs[runs.length - 1];
|
|
188
|
-
if (!latestRun) {
|
|
189
|
-
throw new PDRuntimeError('execution_failed', `No run records found for task ${taskId} after lease acquisition`);
|
|
78
|
+
async invokeRuntime(taskId, context) {
|
|
79
|
+
let parsedPhilosopherArtifact;
|
|
80
|
+
try {
|
|
81
|
+
parsedPhilosopherArtifact = JSON.parse(context.philosopherArtifact);
|
|
190
82
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
async invokeRuntime(params) {
|
|
194
|
-
let parsedPhilosopherArtifact = null;
|
|
195
|
-
if (params.philosopherArtifact) {
|
|
196
|
-
try {
|
|
197
|
-
parsedPhilosopherArtifact = JSON.parse(params.philosopherArtifact);
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
parsedPhilosopherArtifact = params.philosopherArtifact;
|
|
201
|
-
}
|
|
83
|
+
catch {
|
|
84
|
+
parsedPhilosopherArtifact = context.philosopherArtifact;
|
|
202
85
|
}
|
|
203
86
|
const builder = new ScribePromptBuilder();
|
|
204
87
|
const { message } = builder.buildPrompt({
|
|
205
|
-
taskId
|
|
206
|
-
contextHash:
|
|
88
|
+
taskId,
|
|
89
|
+
contextHash: context.contextHash,
|
|
207
90
|
philosopherArtifact: parsedPhilosopherArtifact,
|
|
208
|
-
sourcePhilosopherArtifactId:
|
|
91
|
+
sourcePhilosopherArtifactId: context.sourcePhilosopherArtifactId,
|
|
209
92
|
});
|
|
210
|
-
|
|
93
|
+
return this.runtimeAdapter.startRun({
|
|
211
94
|
agentSpec: { agentId: this.resolvedOptions.agentId, schemaVersion: 'v1' },
|
|
212
|
-
taskRef: { taskId
|
|
95
|
+
taskRef: { taskId },
|
|
213
96
|
inputPayload: message,
|
|
214
97
|
contextItems: [],
|
|
215
98
|
outputSchemaRef: 'scribe-output-v1',
|
|
216
99
|
timeoutMs: this.resolvedOptions.timeoutMs,
|
|
217
|
-
};
|
|
218
|
-
return this.runtimeAdapter.startRun(startInput);
|
|
100
|
+
});
|
|
219
101
|
}
|
|
220
|
-
async
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await this.sleep(this.resolvedOptions.pollIntervalMs);
|
|
102
|
+
async validateOutput(output, taskId, context) {
|
|
103
|
+
const result = await this.validator.validate(output, taskId, context.sourcePhilosopherArtifactId);
|
|
104
|
+
// Trust-boundary: validator is an injected dependency returning `string | undefined`
|
|
105
|
+
// for errorCategory. We must not `as`-cast; validate at runtime (ERR-001, ERR-005).
|
|
106
|
+
const rawCategory = result.errorCategory;
|
|
107
|
+
let errorCategory;
|
|
108
|
+
if (rawCategory == null) {
|
|
109
|
+
errorCategory = undefined;
|
|
229
110
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
await this.runtimeAdapter.cancelRun(runHandle.runId);
|
|
111
|
+
else if (isPDErrorCategory(rawCategory)) {
|
|
112
|
+
errorCategory = rawCategory;
|
|
233
113
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
114
|
+
else {
|
|
115
|
+
// Invalid errorCategory from validator — fail loud, do not pass through
|
|
116
|
+
return {
|
|
117
|
+
valid: false,
|
|
118
|
+
errors: [...result.errors, `invalid errorCategory: ${rawCategory}`],
|
|
119
|
+
errorCategory: 'output_invalid',
|
|
120
|
+
};
|
|
240
121
|
}
|
|
241
|
-
|
|
242
|
-
|
|
122
|
+
return {
|
|
123
|
+
valid: result.valid,
|
|
124
|
+
errors: result.errors,
|
|
125
|
+
errorCategory,
|
|
126
|
+
};
|
|
243
127
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
129
|
+
async succeedTask(taskId, runId, output, task, contextHash, context) {
|
|
130
|
+
// Lineage consistency: sourcePhilosopherArtifactId must match buildContext result (ERR-004).
|
|
131
|
+
if (output.sourcePhilosopherArtifactId !== context.sourcePhilosopherArtifactId) {
|
|
132
|
+
throw new PDRuntimeError('output_invalid', `sourcePhilosopherArtifactId mismatch: expected ${context.sourcePhilosopherArtifactId}, got ${output.sourcePhilosopherArtifactId}`);
|
|
248
133
|
}
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
async succeedTask(ctx) {
|
|
134
|
+
// Store output before marking succeeded
|
|
252
135
|
try {
|
|
253
|
-
await this.stateManager.updateRunOutput(
|
|
136
|
+
await this.stateManager.updateRunOutput(runId, JSON.stringify(output));
|
|
254
137
|
}
|
|
255
138
|
catch (updateErr) {
|
|
256
|
-
this.
|
|
257
|
-
runId
|
|
139
|
+
this.emitEvent('update_output_failed', taskId, {
|
|
140
|
+
runId,
|
|
258
141
|
errorMessage: updateErr instanceof Error ? updateErr.message : String(updateErr),
|
|
259
142
|
});
|
|
260
143
|
throw updateErr;
|
|
261
144
|
}
|
|
262
|
-
|
|
263
|
-
const now = new Date().toISOString();
|
|
145
|
+
// Resolve lineage artifact IDs
|
|
264
146
|
let lineageArtifactIds = [];
|
|
147
|
+
let lineageHasRejected = false;
|
|
265
148
|
try {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
149
|
+
const lineageResult = await this.resolveLineageArtifactIds(taskId);
|
|
150
|
+
lineageArtifactIds = lineageResult.ids;
|
|
151
|
+
lineageHasRejected = lineageResult.hasRejected;
|
|
152
|
+
}
|
|
153
|
+
catch (lineageErr) {
|
|
154
|
+
this.emitEvent('lineage_resolve_failed', taskId, {
|
|
155
|
+
runId,
|
|
156
|
+
errorMessage: lineageErr instanceof Error ? lineageErr.message : String(lineageErr),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (lineageHasRejected) {
|
|
160
|
+
this.emitEvent('lineage_partial', taskId, {
|
|
161
|
+
runId,
|
|
162
|
+
resolvedCount: lineageArtifactIds.length,
|
|
163
|
+
warning: 'Some dependency artifact queries were rejected; lineage may be incomplete',
|
|
164
|
+
});
|
|
276
165
|
}
|
|
277
|
-
|
|
166
|
+
// Write PIArtifact via artifactStore (idempotent upsert)
|
|
167
|
+
const artifactId = `pi-art-${taskId}-${runId}`;
|
|
168
|
+
const now = new Date().toISOString();
|
|
278
169
|
try {
|
|
279
170
|
await this.artifactStore.upsertArtifact({
|
|
280
171
|
artifactId,
|
|
281
172
|
artifactKind: 'principle',
|
|
282
|
-
sourceTaskId:
|
|
173
|
+
sourceTaskId: taskId,
|
|
283
174
|
lineageArtifactIds,
|
|
284
175
|
validationStatus: 'pending',
|
|
285
|
-
contentJson: JSON.stringify(
|
|
176
|
+
contentJson: JSON.stringify(output),
|
|
286
177
|
createdAt: now,
|
|
287
178
|
updatedAt: now,
|
|
288
179
|
});
|
|
289
180
|
}
|
|
290
181
|
catch (artifactErr) {
|
|
291
|
-
this.
|
|
292
|
-
runId
|
|
182
|
+
this.emitEvent('artifact_write_failed', taskId, {
|
|
183
|
+
runId,
|
|
293
184
|
errorMessage: artifactErr instanceof Error ? artifactErr.message : String(artifactErr),
|
|
294
185
|
});
|
|
295
186
|
return this.retryOrFail({
|
|
296
|
-
taskId
|
|
297
|
-
task
|
|
187
|
+
taskId,
|
|
188
|
+
task,
|
|
298
189
|
errorCategory: 'artifact_commit_failed',
|
|
299
190
|
failureReason: `PIArtifact write failed: ${artifactErr instanceof Error ? artifactErr.message : String(artifactErr)}`,
|
|
300
191
|
});
|
|
301
192
|
}
|
|
302
|
-
|
|
193
|
+
// Mark task succeeded
|
|
194
|
+
const resultRef = `${this.config.resultRefPrefix}://${runId}`;
|
|
303
195
|
try {
|
|
304
|
-
await this.stateManager.markTaskSucceeded(
|
|
196
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
305
197
|
}
|
|
306
198
|
catch (stateErr) {
|
|
307
|
-
this.
|
|
308
|
-
taskId
|
|
309
|
-
runId
|
|
199
|
+
this.emitEvent('mark_succeeded_failed', taskId, {
|
|
200
|
+
taskId,
|
|
201
|
+
runId,
|
|
310
202
|
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
311
203
|
});
|
|
312
204
|
throw stateErr;
|
|
313
205
|
}
|
|
314
|
-
this.
|
|
315
|
-
attemptCount:
|
|
206
|
+
this.emitEvent('task_succeeded', taskId, {
|
|
207
|
+
attemptCount: task.attemptCount,
|
|
316
208
|
resultRef,
|
|
317
|
-
principleTitle:
|
|
209
|
+
principleTitle: output.principleDraft.title,
|
|
318
210
|
});
|
|
319
|
-
this.phase = RunnerPhase.Completed;
|
|
320
211
|
return {
|
|
321
212
|
status: 'succeeded',
|
|
322
|
-
taskId
|
|
323
|
-
runId
|
|
213
|
+
taskId,
|
|
214
|
+
runId,
|
|
324
215
|
artifactId,
|
|
325
216
|
resultRef,
|
|
326
|
-
contextHash
|
|
327
|
-
output
|
|
328
|
-
attemptCount:
|
|
217
|
+
contextHash,
|
|
218
|
+
output,
|
|
219
|
+
attemptCount: task.attemptCount,
|
|
329
220
|
};
|
|
330
221
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return this.retryOrFail({
|
|
338
|
-
taskId,
|
|
339
|
-
task,
|
|
340
|
-
errorCategory,
|
|
341
|
-
failureReason: `Runtime execution ended with status: ${runStatus.status}`,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
async handleValidationError(ctx) {
|
|
345
|
-
const category = (ctx.errorCategory ?? 'output_invalid');
|
|
346
|
-
this.emitScribeEvent('scribe_output_invalid', ctx.taskId, {
|
|
347
|
-
errorCount: ctx.errors.length,
|
|
348
|
-
errorCategory: category,
|
|
349
|
-
});
|
|
350
|
-
return this.retryOrFail({
|
|
351
|
-
taskId: ctx.taskId,
|
|
352
|
-
task: ctx.task,
|
|
353
|
-
errorCategory: category,
|
|
354
|
-
failureReason: `Validation failed: ${ctx.errors.join('; ')}`,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
async handleLeaseOrPhaseError(taskId, error) {
|
|
358
|
-
const classified = this.classifyError(error);
|
|
359
|
-
if (classified.category === 'lease_conflict') {
|
|
360
|
-
this.emitScribeEvent('scribe_run_failed', taskId, {
|
|
361
|
-
errorCategory: 'lease_conflict',
|
|
362
|
-
errorMessage: classified.message,
|
|
363
|
-
});
|
|
364
|
-
return {
|
|
365
|
-
status: 'failed',
|
|
366
|
-
taskId,
|
|
367
|
-
errorCategory: 'lease_conflict',
|
|
368
|
-
failureReason: classified.message,
|
|
369
|
-
attemptCount: 1,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
this.emitScribeEvent('scribe_run_failed', taskId, {
|
|
373
|
-
errorCategory: classified.category,
|
|
374
|
-
errorMessage: classified.message,
|
|
375
|
-
});
|
|
376
|
-
const task = {
|
|
377
|
-
taskId,
|
|
378
|
-
taskKind: 'scribe',
|
|
379
|
-
status: 'leased',
|
|
380
|
-
createdAt: new Date().toISOString(),
|
|
381
|
-
updatedAt: new Date().toISOString(),
|
|
382
|
-
attemptCount: 1,
|
|
383
|
-
maxAttempts: this.resolvedOptions.defaultMaxAttempts,
|
|
384
|
-
};
|
|
385
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
386
|
-
}
|
|
387
|
-
async handlePostLeaseError(taskId, task, error) {
|
|
388
|
-
const classified = this.classifyError(error);
|
|
389
|
-
this.emitScribeEvent('scribe_run_failed', taskId, {
|
|
390
|
-
errorCategory: classified.category,
|
|
391
|
-
errorMessage: classified.message,
|
|
392
|
-
});
|
|
393
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
394
|
-
}
|
|
395
|
-
async retryOrFail(ctx) {
|
|
396
|
-
if (this.isPermanentError(ctx.errorCategory)) {
|
|
397
|
-
try {
|
|
398
|
-
await this.stateManager.markTaskFailed(ctx.taskId, ctx.errorCategory);
|
|
399
|
-
}
|
|
400
|
-
catch (stateErr) {
|
|
401
|
-
this.emitScribeEvent('scribe_mark_failed_error', ctx.taskId, {
|
|
402
|
-
errorCategory: 'storage_unavailable',
|
|
403
|
-
attemptCount: ctx.task.attemptCount,
|
|
404
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
405
|
-
});
|
|
406
|
-
return {
|
|
407
|
-
status: 'failed',
|
|
408
|
-
taskId: ctx.taskId,
|
|
409
|
-
errorCategory: 'storage_unavailable',
|
|
410
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
411
|
-
attemptCount: ctx.task.attemptCount,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
this.emitScribeEvent('scribe_task_failed', ctx.taskId, {
|
|
415
|
-
errorCategory: ctx.errorCategory,
|
|
416
|
-
attemptCount: ctx.task.attemptCount,
|
|
417
|
-
failureReason: ctx.failureReason,
|
|
418
|
-
});
|
|
419
|
-
this.phase = RunnerPhase.Failed;
|
|
420
|
-
return {
|
|
421
|
-
status: 'failed',
|
|
422
|
-
taskId: ctx.taskId,
|
|
423
|
-
errorCategory: ctx.errorCategory,
|
|
424
|
-
failureReason: ctx.failureReason,
|
|
425
|
-
attemptCount: ctx.task.attemptCount,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
const shouldRetry = this.stateManager.getRetryPolicy().shouldRetry(ctx.task);
|
|
429
|
-
if (shouldRetry) {
|
|
430
|
-
try {
|
|
431
|
-
await this.stateManager.markTaskRetryWait(ctx.taskId, ctx.errorCategory);
|
|
432
|
-
}
|
|
433
|
-
catch (stateErr) {
|
|
434
|
-
this.emitScribeEvent('scribe_mark_retry_error', ctx.taskId, {
|
|
435
|
-
errorCategory: 'storage_unavailable',
|
|
436
|
-
attemptCount: ctx.task.attemptCount,
|
|
437
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
438
|
-
});
|
|
439
|
-
return {
|
|
440
|
-
status: 'failed',
|
|
441
|
-
taskId: ctx.taskId,
|
|
442
|
-
errorCategory: 'storage_unavailable',
|
|
443
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
444
|
-
attemptCount: ctx.task.attemptCount,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
this.emitScribeEvent('scribe_task_retried', ctx.taskId, {
|
|
448
|
-
errorCategory: ctx.errorCategory,
|
|
449
|
-
attemptCount: ctx.task.attemptCount,
|
|
450
|
-
});
|
|
451
|
-
this.phase = RunnerPhase.RetryWaiting;
|
|
452
|
-
return {
|
|
453
|
-
status: 'retried',
|
|
454
|
-
taskId: ctx.taskId,
|
|
455
|
-
errorCategory: ctx.errorCategory,
|
|
456
|
-
failureReason: ctx.failureReason,
|
|
457
|
-
attemptCount: ctx.task.attemptCount,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
try {
|
|
461
|
-
await this.stateManager.markTaskFailed(ctx.taskId, 'max_attempts_exceeded');
|
|
462
|
-
}
|
|
463
|
-
catch (stateErr) {
|
|
464
|
-
this.emitScribeEvent('scribe_mark_failed_error', ctx.taskId, {
|
|
465
|
-
errorCategory: 'storage_unavailable',
|
|
466
|
-
attemptCount: ctx.task.attemptCount,
|
|
467
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
468
|
-
});
|
|
469
|
-
return {
|
|
470
|
-
status: 'failed',
|
|
471
|
-
taskId: ctx.taskId,
|
|
472
|
-
errorCategory: 'storage_unavailable',
|
|
473
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
474
|
-
attemptCount: ctx.task.attemptCount,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
this.emitScribeEvent('scribe_task_failed', ctx.taskId, {
|
|
478
|
-
errorCategory: 'max_attempts_exceeded',
|
|
479
|
-
attemptCount: ctx.task.attemptCount,
|
|
480
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
481
|
-
});
|
|
482
|
-
this.phase = RunnerPhase.Failed;
|
|
483
|
-
return {
|
|
484
|
-
status: 'failed',
|
|
485
|
-
taskId: ctx.taskId,
|
|
486
|
-
errorCategory: 'max_attempts_exceeded',
|
|
487
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
488
|
-
attemptCount: ctx.task.attemptCount,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
PERMANENT_ERROR_CATEGORIES = new Set(Object.freeze(['storage_unavailable', 'workspace_invalid', 'capability_missing', 'cancelled', 'input_invalid', 'output_invalid']));
|
|
492
|
-
isPermanentError(category) {
|
|
493
|
-
return this.PERMANENT_ERROR_CATEGORIES.has(category);
|
|
494
|
-
}
|
|
222
|
+
// ── Optional hooks ─────────────────────────────────────────────────────────
|
|
223
|
+
/**
|
|
224
|
+
* Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
225
|
+
* Only fill when absent via Object.hasOwn — present-but-falsy values
|
|
226
|
+
* must reach validation and fail loud (Runtime Contract Rule 3).
|
|
227
|
+
*/
|
|
495
228
|
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return { category: error.category, message: error.message };
|
|
499
|
-
}
|
|
500
|
-
if (error instanceof Error) {
|
|
501
|
-
return { category: 'execution_failed', message: error.message };
|
|
502
|
-
}
|
|
503
|
-
return { category: 'execution_failed', message: String(error) };
|
|
229
|
+
postFetchTransform(taskId, untrustedOutput) {
|
|
230
|
+
injectRunnerLineageIfAbsent(untrustedOutput, 'taskId', taskId);
|
|
504
231
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
case 'timed_out': return 'timeout';
|
|
510
|
-
case 'cancelled': return 'cancelled';
|
|
511
|
-
default: return 'execution_failed';
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
515
|
-
sleep(ms) {
|
|
516
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
232
|
+
emitSuccessTelemetry(taskId, output) {
|
|
233
|
+
this.emitEvent('principle_draft_generated', taskId, {
|
|
234
|
+
principleTitle: output.principleDraft.title,
|
|
235
|
+
});
|
|
517
236
|
}
|
|
518
237
|
}
|
|
519
|
-
export { resolveScribeRunnerOptions, DEFAULT_SCRIBE_RUNNER_OPTIONS };
|
|
520
238
|
//# sourceMappingURL=scribe-runner.js.map
|