@principles/core 1.270.5 → 1.271.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__/evaluator-repair-loop.test.js +20 -0
- package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +26 -5
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -1
- package/dist/runtime-v2/index.d.ts +7 -1
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +4 -0
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/evaluator-prompt-builder.test.js +1 -1
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +15 -14
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js +56 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js +883 -0
- package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js +328 -0
- package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js.map +1 -0
- package/dist/runtime-v2/internalization/evaluator-output.d.ts +63 -2
- package/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-output.js +139 -2
- package/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +54 -2
- package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -2
- package/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts +34 -0
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +295 -12
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/internalization-orchestrator.js +10 -54
- package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
- package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +7 -1
- package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/internalization-transition-decision.js +9 -2
- package/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -1
- package/dist/runtime-v2/internalization/owner-resolution-service.d.ts +89 -0
- package/dist/runtime-v2/internalization/owner-resolution-service.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/owner-resolution-service.js +272 -0
- package/dist/runtime-v2/internalization/owner-resolution-service.js.map +1 -0
- package/dist/runtime-v2/internalization/owner-retry.d.ts +12 -0
- package/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/owner-retry.js +23 -0
- package/dist/runtime-v2/internalization/owner-retry.js.map +1 -1
- package/dist/runtime-v2/internalization/owner-review.d.ts +195 -0
- package/dist/runtime-v2/internalization/owner-review.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/owner-review.js +470 -0
- package/dist/runtime-v2/internalization/owner-review.js.map +1 -0
- package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +5 -1
- package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
- package/dist/runtime-v2/internalization/pitask-metadata.d.ts +68 -0
- package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/pitask-metadata.js +104 -0
- package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
- package/dist/runtime-v2/internalization/revision-reopen.d.ts +56 -0
- package/dist/runtime-v2/internalization/revision-reopen.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/revision-reopen.js +144 -0
- package/dist/runtime-v2/internalization/revision-reopen.js.map +1 -0
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +12 -0
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.js +145 -14
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
- package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js +1 -0
- package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js.map +1 -1
- package/dist/runtime-v2/store/runtime-state-manager.d.ts +6 -0
- package/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
- package/dist/runtime-v2/store/runtime-state-manager.js +9 -0
- package/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts +2 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts.map +1 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js +78 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js.map +1 -0
- package/dist/runtime-v2/store/task/memory-task-store.d.ts +1 -0
- package/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/memory-task-store.js +8 -0
- package/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.d.ts +1 -0
- package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.js +52 -0
- package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
- package/dist/runtime-v2/store/task/task-store.d.ts +9 -0
- package/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/workspace-isolation.test.js +1 -0
- package/dist/runtime-v2/store/workspace-isolation.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isEvaluatorOutputV2 } from './evaluator-output.js';
|
|
2
2
|
import { PDRuntimeError, isPDErrorCategory } from '../error-categories.js';
|
|
3
3
|
import { hydratePITaskRecord, createPITaskDiagnosticJson, mergePITaskMetadata } from './pitask-metadata.js';
|
|
4
|
-
import {
|
|
4
|
+
import { HUMAN_REVIEW_REASON, planOwnerVerdictOverrideResume, markOwnerResolutionApplied, computeArtifactContentHash, } from './owner-review.js';
|
|
5
|
+
import { EvaluatorPromptBuilder, deriveRequirementLedger } from './evaluator-prompt-builder.js';
|
|
5
6
|
import { reconcileLineageEcho } from './peer-runner-contracts.js';
|
|
6
7
|
import { BasePeerRunner } from '../runner/base-peer-runner.js';
|
|
7
8
|
import { EVALUATOR_STAGE1_MANIFEST, EVALUATOR_STAGE2_MANIFEST } from './context-manifests.js';
|
|
@@ -132,6 +133,8 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
132
133
|
* Null when the deps did not inject the seeder (= repair seeding unavailable).
|
|
133
134
|
*/
|
|
134
135
|
repairTaskSeeder;
|
|
136
|
+
/** PRI-630: runtime-authoritative tool facts; null = catalog unavailable (degraded rule in prompt) */
|
|
137
|
+
hostToolCatalog;
|
|
135
138
|
constructor(deps, options) {
|
|
136
139
|
super(deps, options, {
|
|
137
140
|
runnerName: 'evaluator',
|
|
@@ -143,6 +146,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
143
146
|
this.gateDeps = options.gateDeps ?? null;
|
|
144
147
|
this.repairLoopEnabledResolver = deps.isRepairLoopEnabled ?? null;
|
|
145
148
|
this.repairTaskSeeder = deps.seedArtificerRepairTask ?? null;
|
|
149
|
+
this.hostToolCatalog = options.hostToolCatalog ?? null;
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
152
|
* Returns true iff the evaluator→artificer repair loop feature flag is on.
|
|
@@ -179,6 +183,8 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
179
183
|
});
|
|
180
184
|
continue;
|
|
181
185
|
}
|
|
186
|
+
const depPi = hydratePITaskRecord(depTask);
|
|
187
|
+
const dependencyRepairPayload = depPi?.repairPayload;
|
|
182
188
|
const artifacts = await this.artifactStore.listBySourceTaskId(depId);
|
|
183
189
|
if (artifacts.length > 0) {
|
|
184
190
|
const [firstArtifact] = artifacts;
|
|
@@ -211,12 +217,19 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
211
217
|
const contextRefs = scribeContent && scribeRef
|
|
212
218
|
? [artifactRef, scribeRef]
|
|
213
219
|
: [artifactRef];
|
|
220
|
+
// PRI-630: 修复轮 (repairPayload 存在) 时解析上轮评估上下文
|
|
221
|
+
let previousEvaluation;
|
|
222
|
+
if (dependencyRepairPayload) {
|
|
223
|
+
previousEvaluation = await this.resolvePreviousEvaluation(taskId, dependencyRepairPayload, firstArtifact.contentJson);
|
|
224
|
+
}
|
|
214
225
|
return {
|
|
215
226
|
contextHash: BasePeerRunner.hashContextRefs(contextRefs),
|
|
216
227
|
artificerArtifact: firstArtifact.contentJson,
|
|
217
228
|
sourceArtificerArtifactId: firstArtifact.artifactId,
|
|
218
229
|
scribeArtifact: scribeContent,
|
|
219
230
|
sourceScribeArtifactId: scribeRef,
|
|
231
|
+
...(dependencyRepairPayload !== undefined ? { dependencyRepairPayload } : {}),
|
|
232
|
+
...(previousEvaluation !== undefined ? { previousEvaluation } : {}),
|
|
220
233
|
};
|
|
221
234
|
}
|
|
222
235
|
}
|
|
@@ -312,6 +325,116 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
312
325
|
* Build the evaluator prompt with the given manifest's resolved context.
|
|
313
326
|
* Shared by single-stage and two-stage paths.
|
|
314
327
|
*/
|
|
328
|
+
/**
|
|
329
|
+
* PRI-630 收敛契约 (SPEC §18.1): 解析上轮评估上下文 — 从 dependency
|
|
330
|
+
* artificer 的 repairPayload.sourceEvaluatorTaskId 找到上轮 evaluator,
|
|
331
|
+
* 读取其最近 principle artifact,按 rc-1/rc-2 守卫解析 evaluation 字段。
|
|
332
|
+
* requirements 用稳定 id (req-1..N, 上轮 requiredChanges 顺序)。
|
|
333
|
+
* 解析失败 → 结构化降级事件 + undefined (保持既有行为,可观测)。
|
|
334
|
+
*/
|
|
335
|
+
async resolvePreviousEvaluation(taskId, repairPayload, repairArtifactContentJson) {
|
|
336
|
+
const priorTaskId = repairPayload.sourceEvaluatorTaskId;
|
|
337
|
+
const priorArtifactJson = await this.artifactStore
|
|
338
|
+
.listBySourceTaskId(priorTaskId)
|
|
339
|
+
.then((artifacts) => {
|
|
340
|
+
let latest = null;
|
|
341
|
+
for (const a of artifacts) {
|
|
342
|
+
if (a.artifactKind !== 'principle')
|
|
343
|
+
continue;
|
|
344
|
+
if (!latest || a.updatedAt > latest.updatedAt)
|
|
345
|
+
latest = a;
|
|
346
|
+
}
|
|
347
|
+
return latest?.contentJson ?? null;
|
|
348
|
+
})
|
|
349
|
+
.catch(() => null);
|
|
350
|
+
if (priorArtifactJson === null) {
|
|
351
|
+
this.emitEvent('previous_evaluation_context_degraded', taskId, {
|
|
352
|
+
priorEvaluatorTaskId: priorTaskId,
|
|
353
|
+
reason: 'prior_evaluation_artifact_unavailable',
|
|
354
|
+
repairIteration: repairPayload.repairIteration,
|
|
355
|
+
});
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
let parsed;
|
|
359
|
+
try {
|
|
360
|
+
parsed = JSON.parse(priorArtifactJson);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
this.emitEvent('previous_evaluation_context_degraded', taskId, {
|
|
364
|
+
priorEvaluatorTaskId: priorTaskId,
|
|
365
|
+
reason: 'prior_evaluation_artifact_unparseable',
|
|
366
|
+
});
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
370
|
+
return undefined;
|
|
371
|
+
// runtime-contract-exempt: ERR-001 object-guarded unknown property extraction; typeof guard follows immediately
|
|
372
|
+
const { evaluation } = parsed;
|
|
373
|
+
if (typeof evaluation !== 'object' || evaluation === null) {
|
|
374
|
+
this.emitEvent('previous_evaluation_context_degraded', taskId, {
|
|
375
|
+
priorEvaluatorTaskId: priorTaskId,
|
|
376
|
+
reason: 'prior_evaluation_shape_invalid',
|
|
377
|
+
});
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
const ev = evaluation;
|
|
381
|
+
const decision = typeof ev.decision === 'string' ? ev.decision : 'needs_revision';
|
|
382
|
+
const score = typeof ev.score === 'number' && Number.isFinite(ev.score) ? Math.min(1, Math.max(0, ev.score)) : 0;
|
|
383
|
+
const toStringArray = (v, cap) => {
|
|
384
|
+
if (!Array.isArray(v))
|
|
385
|
+
return [];
|
|
386
|
+
const out = [];
|
|
387
|
+
for (const item of v.slice(0, cap)) {
|
|
388
|
+
if (typeof item === 'string' && item.trim() !== '')
|
|
389
|
+
out.push(item.slice(0, 500));
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
};
|
|
393
|
+
const concerns = toStringArray(ev.concerns, 10);
|
|
394
|
+
const requiredChanges = toStringArray(ev.requiredChanges, 10);
|
|
395
|
+
// PRI-630 P1 评审修复: 需求身份跨轮稳定 — 上轮 echo 的 requirementLedger
|
|
396
|
+
// (若有) 中 still_open/regressed 条目保留原 id 与原 statement;本轮
|
|
397
|
+
// requiredChanges 的非重述项作为新需求从最大序号递增。无 ledger 时
|
|
398
|
+
// (首个修复轮) 退回顺序编号。
|
|
399
|
+
let prevLedger;
|
|
400
|
+
if (Array.isArray(ev.requirementLedger)) {
|
|
401
|
+
prevLedger = [];
|
|
402
|
+
for (const entry of ev.requirementLedger) {
|
|
403
|
+
if (entry === null || typeof entry !== 'object')
|
|
404
|
+
continue;
|
|
405
|
+
const rec = entry;
|
|
406
|
+
if (typeof rec.id !== 'string' || typeof rec.statement !== 'string')
|
|
407
|
+
continue;
|
|
408
|
+
if (rec.status !== 'resolved' && rec.status !== 'still_open' && rec.status !== 'regressed')
|
|
409
|
+
continue;
|
|
410
|
+
prevLedger.push({ id: rec.id, statement: rec.statement, status: rec.status });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const requirements = deriveRequirementLedger(prevLedger, requiredChanges);
|
|
414
|
+
// 修复说明: 当前 (被修复) artificer artifact 的声明性摘要 — 有界提取
|
|
415
|
+
let repairSummary;
|
|
416
|
+
try {
|
|
417
|
+
const art = JSON.parse(repairArtifactContentJson);
|
|
418
|
+
if (typeof art === 'object' && art !== null) {
|
|
419
|
+
// runtime-contract-exempt: ERR-001 object-guarded unknown property extraction; typeof guard follows immediately
|
|
420
|
+
const { implementationSummary } = art;
|
|
421
|
+
if (typeof implementationSummary === 'string' && implementationSummary.trim() !== '') {
|
|
422
|
+
repairSummary = implementationSummary.slice(0, 800);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
repairSummary = undefined;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
decision,
|
|
431
|
+
score,
|
|
432
|
+
concerns,
|
|
433
|
+
requirements,
|
|
434
|
+
repairIteration: repairPayload.repairIteration,
|
|
435
|
+
...(repairSummary !== undefined ? { repairSummary } : {}),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
315
438
|
buildEvaluatorPrompt(taskId, context, manifest) {
|
|
316
439
|
let parsedArtificerArtifact = null;
|
|
317
440
|
if (context.artificerArtifact) {
|
|
@@ -346,6 +469,8 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
346
469
|
artificerArtifact: parsedArtificerArtifact,
|
|
347
470
|
scribeArtifact: parsedScribeArtifact,
|
|
348
471
|
sourceArtificerArtifactId: context.sourceArtificerArtifactId ?? '',
|
|
472
|
+
previousEvaluation: context.previousEvaluation,
|
|
473
|
+
hostToolCatalog: this.hostToolCatalog ?? undefined,
|
|
349
474
|
});
|
|
350
475
|
return message;
|
|
351
476
|
}
|
|
@@ -362,7 +487,12 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
362
487
|
});
|
|
363
488
|
}
|
|
364
489
|
async validateOutput(output, taskId, context) {
|
|
365
|
-
|
|
490
|
+
// PRI-630 P1 评审修复: 修复轮把上轮 requirement ids 传入做完整覆盖校验
|
|
491
|
+
// (缺 priorRequirementStatuses 或漏 id → output_invalid),不再仅靠 prompt。
|
|
492
|
+
const convergence = context.previousEvaluation
|
|
493
|
+
? { expectedRequirements: context.previousEvaluation.requirements.map((r) => ({ id: r.id, statement: r.statement })) }
|
|
494
|
+
: undefined;
|
|
495
|
+
const result = await this.validator.validate(output, taskId, context.sourceArtificerArtifactId ?? undefined, convergence);
|
|
366
496
|
// Trust-boundary: validator is an injected dependency returning `string | undefined`
|
|
367
497
|
// for errorCategory. We must not `as`-cast; validate at runtime (ERR-001, ERR-005).
|
|
368
498
|
const rawCategory = result.errorCategory;
|
|
@@ -568,7 +698,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
568
698
|
*/
|
|
569
699
|
async applyEvaluatorDecisionEffects(args) {
|
|
570
700
|
const { taskId, runId, finalOutput, task, artifactId, contextHash, sourceArtificerArtifactId } = args;
|
|
571
|
-
const
|
|
701
|
+
const decision = args.decisionOverride ?? finalOutput.evaluation.decision;
|
|
572
702
|
// ── Evaluator-specific: validate principle-bearing Scribe artifact ──
|
|
573
703
|
// This is the critical business logic: approved evaluator must validate
|
|
574
704
|
// the Scribe principle artifact, NOT the Artificer plan artifact.
|
|
@@ -632,6 +762,10 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
632
762
|
if (decision === 'needs_revision' && this.isRepairLoopEnabled()) {
|
|
633
763
|
const repairOutcome = await this.maybeSeedArtificerRepair(taskId, { runId, output: finalOutput, sourceArtificerArtifactId });
|
|
634
764
|
if (repairOutcome.kind === 'max_iterations_reached') {
|
|
765
|
+
// PRI-629: budget 耗尽(decision-capable)与 seed 失败(recovery)拆分原因码
|
|
766
|
+
const reasonCode = repairOutcome.detail === 'budget_exhausted'
|
|
767
|
+
? HUMAN_REVIEW_REASON.evaluatorRepairBudgetExhausted
|
|
768
|
+
: HUMAN_REVIEW_REASON.evaluatorRepairSeedFailed;
|
|
635
769
|
// Fail loud (rc-9, EP-03, ERR-002): mark the task needs_human_review
|
|
636
770
|
// so it does NOT stay in 'leased' state (which would cause the lease
|
|
637
771
|
// to expire and the evaluator to re-run the same verdict infinitely).
|
|
@@ -639,14 +773,14 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
639
773
|
// fail-closed: 写失败 throw → retry_wait → 入口门 resume 同一效果,
|
|
640
774
|
// 不问 LLM;禁止吞错后继续 (intent applied ⇔ effect 已 durable)。
|
|
641
775
|
const resultRef = `${this.config.resultRefPrefix}://${runId}`;
|
|
642
|
-
await this.markNeedsHumanReviewOrThrow(taskId, runId,
|
|
776
|
+
await this.markNeedsHumanReviewOrThrow(taskId, { runId, reasonCode, sourceArtifactId: artifactId });
|
|
643
777
|
this.emitEvent('task_needs_human_review', taskId, {
|
|
644
778
|
attemptCount: task.attemptCount,
|
|
645
779
|
resultRef,
|
|
646
780
|
evaluationDecision: finalOutput.evaluation.decision,
|
|
647
781
|
evaluationScore: finalOutput.evaluation.score,
|
|
648
782
|
ruleArtifactId: null,
|
|
649
|
-
reason:
|
|
783
|
+
reason: `repair_loop_${reasonCode}`,
|
|
650
784
|
});
|
|
651
785
|
return {
|
|
652
786
|
kind: 'human_review',
|
|
@@ -711,9 +845,39 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
711
845
|
* effect,不问 LLM;禁止吞错后让 caller 标 intent applied
|
|
712
846
|
* (intent applied ⇔ 其 durable effect 已 materialize)。
|
|
713
847
|
*/
|
|
714
|
-
async markNeedsHumanReviewOrThrow(taskId,
|
|
848
|
+
async markNeedsHumanReviewOrThrow(taskId, review) {
|
|
849
|
+
const { runId, reasonCode, sourceArtifactId } = review;
|
|
715
850
|
try {
|
|
716
|
-
|
|
851
|
+
// PRI-629: status + humanReviewContext 同一次 task-row mutation 原子落库
|
|
852
|
+
// (SPEC §4 — context 缺失的 NHR 是 legacy,只能靠推断)。
|
|
853
|
+
const raw = await this.stateManager.getTask(taskId);
|
|
854
|
+
if (!raw)
|
|
855
|
+
throw new Error(`task ${taskId} not found`);
|
|
856
|
+
const piTask = hydratePITaskRecord(raw);
|
|
857
|
+
if (!piTask)
|
|
858
|
+
throw new Error(`task ${taskId} not hydratable`);
|
|
859
|
+
let sourceArtifactHash;
|
|
860
|
+
try {
|
|
861
|
+
const artifact = await this.artifactStore.getArtifactById(sourceArtifactId);
|
|
862
|
+
if (artifact)
|
|
863
|
+
sourceArtifactHash = computeArtifactContentHash(artifact.contentJson);
|
|
864
|
+
}
|
|
865
|
+
catch {
|
|
866
|
+
sourceArtifactHash = undefined; // hash 可选 — capability 侧要求 artifact 存在,届时重算
|
|
867
|
+
}
|
|
868
|
+
const context = {
|
|
869
|
+
reasonCode,
|
|
870
|
+
sourceRunId: runId,
|
|
871
|
+
sourceArtifactId,
|
|
872
|
+
...(sourceArtifactHash !== undefined ? { sourceArtifactHash } : {}),
|
|
873
|
+
revisionEpoch: piTask.revisionCount ?? 0,
|
|
874
|
+
createdAt: new Date().toISOString(),
|
|
875
|
+
};
|
|
876
|
+
const merged = mergePITaskMetadata(piTask, { humanReviewContext: context });
|
|
877
|
+
await this.stateManager.updateTask(taskId, {
|
|
878
|
+
status: 'needs_human_review',
|
|
879
|
+
diagnosticJson: createPITaskDiagnosticJson(merged),
|
|
880
|
+
});
|
|
717
881
|
// read-back invariant (INV-2): 只有 effect durable 才允许 caller 标 applied
|
|
718
882
|
const current = await this.stateManager.getTask(taskId);
|
|
719
883
|
if (!current || current.status !== 'needs_human_review') {
|
|
@@ -723,7 +887,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
723
887
|
catch (err) {
|
|
724
888
|
this.emitEvent('repair_loop_mark_review_failed', taskId, {
|
|
725
889
|
runId,
|
|
726
|
-
reason,
|
|
890
|
+
reason: reasonCode,
|
|
727
891
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
728
892
|
nextAction: 'task_will_retry_then_resume_completion_intent_without_llm',
|
|
729
893
|
});
|
|
@@ -797,6 +961,14 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
797
961
|
*/
|
|
798
962
|
async maybeResumePendingIntent(taskId, leasedTask) {
|
|
799
963
|
const piTask = hydratePITaskRecord(leasedTask);
|
|
964
|
+
// ── PRI-629: pending Owner Resolution 优先于一切 (SPEC §10) ──
|
|
965
|
+
// Owner accept_current / reject_current 已 durable 记录且任务被翻回
|
|
966
|
+
// pending — 本次 run 应用 override,绝不重新调用 LLM。applied 但未
|
|
967
|
+
// terminal 的 crash 窗口同样由此收敛 (SPEC §30)。
|
|
968
|
+
const ownerOverride = piTask ? planOwnerVerdictOverrideResume(piTask) : null;
|
|
969
|
+
if (ownerOverride) {
|
|
970
|
+
return await this.applyOwnerVerdictOverrideAndFinalize(taskId, leasedTask, ownerOverride);
|
|
971
|
+
}
|
|
800
972
|
const intent = piTask?.completionIntent;
|
|
801
973
|
if (!piTask || !intent || intent.status !== 'pending') {
|
|
802
974
|
// P0 (INV-1/INV-5): applied 但任务未 terminal (标 applied 后、
|
|
@@ -925,6 +1097,117 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
925
1097
|
attemptCount: leasedTask.attemptCount,
|
|
926
1098
|
};
|
|
927
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* PRI-629: 应用 Owner verdict override 并收敛 terminal。
|
|
1102
|
+
*
|
|
1103
|
+
* 顺序 (SPEC §10/§30): 恢复 durable output → 幂等效果 (override decision)
|
|
1104
|
+
* → completion intent 标 applied → resolution 标 applied → markTaskSucceeded。
|
|
1105
|
+
* 任何 crash 窗口重放同一 resolution,不重新调用 LLM。机器 verdict
|
|
1106
|
+
* (runnerDecision) 永不改写。
|
|
1107
|
+
*/
|
|
1108
|
+
async applyOwnerVerdictOverrideAndFinalize(taskId, leasedTask, plan) {
|
|
1109
|
+
const { resolution, overrideDecision } = plan;
|
|
1110
|
+
this.emitEvent('owner_resolution_applying', taskId, {
|
|
1111
|
+
resolutionId: resolution.resolutionId,
|
|
1112
|
+
action: resolution.action,
|
|
1113
|
+
machineDecision: resolution.machineDecision,
|
|
1114
|
+
effectiveDecision: overrideDecision,
|
|
1115
|
+
sourceRunId: resolution.sourceRunId,
|
|
1116
|
+
});
|
|
1117
|
+
// 恢复裁决时的 durable output — decision 必须与 resolution 记录的机器判定一致
|
|
1118
|
+
const output = await this.recoverIntentOutput(taskId, resolution.sourceRunId, resolution.machineDecision);
|
|
1119
|
+
// SPEC §20 纵深防御: 确定性对抗门失败不允许 approved override (capability
|
|
1120
|
+
// 层已挡一道;此处防事实漂移窗口)。
|
|
1121
|
+
if (overrideDecision === 'approved'
|
|
1122
|
+
&& isEvaluatorOutputV2(output)
|
|
1123
|
+
&& output.adversarialResult
|
|
1124
|
+
&& output.adversarialResult.passed === false) {
|
|
1125
|
+
this.emitEvent('owner_resolution_rejected_by_policy', taskId, {
|
|
1126
|
+
resolutionId: resolution.resolutionId,
|
|
1127
|
+
reason: 'deterministic_hard_gate_failed',
|
|
1128
|
+
nextAction: 'owner_may_choose_revise_once_or_reject_current',
|
|
1129
|
+
});
|
|
1130
|
+
throw new PDRuntimeError('input_invalid', `Owner accept_current refused for task ${taskId}: deterministic adversarial gate failed (owner cannot override hard safety)`);
|
|
1131
|
+
}
|
|
1132
|
+
const artifactId = resolution.sourceArtifactId;
|
|
1133
|
+
const contextHash = `owner-override-${resolution.resolutionId}`;
|
|
1134
|
+
// rule assembly 输入由 durable lineage 重建 (与 intent resume 相同)
|
|
1135
|
+
const assemblySourceId = output.sourceArtificerArtifactId ?? null;
|
|
1136
|
+
let artificerContent = null;
|
|
1137
|
+
if (assemblySourceId) {
|
|
1138
|
+
try {
|
|
1139
|
+
const rec = await this.artifactStore.getArtifactById(assemblySourceId);
|
|
1140
|
+
artificerContent = rec?.contentJson ?? null;
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
artificerContent = null;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const effectResult = await this.applyEvaluatorDecisionEffects({
|
|
1147
|
+
taskId,
|
|
1148
|
+
runId: resolution.sourceRunId,
|
|
1149
|
+
finalOutput: output,
|
|
1150
|
+
task: leasedTask,
|
|
1151
|
+
artifactId,
|
|
1152
|
+
contextHash,
|
|
1153
|
+
sourceArtificerArtifactId: assemblySourceId,
|
|
1154
|
+
ruleAssemblyInput: { artificerArtifact: artificerContent, sourceArtificerArtifactId: assemblySourceId },
|
|
1155
|
+
decisionOverride: overrideDecision === 'approved' ? 'approved' : 'rejected',
|
|
1156
|
+
});
|
|
1157
|
+
if (effectResult.kind === 'human_review') {
|
|
1158
|
+
// P0 评审修复: 与 rollout 对称——override 驱动的 effects 落入 recovery
|
|
1159
|
+
// NHR 时,Owner 裁决已被执行,resolution 标 applied (否则 pending 残留
|
|
1160
|
+
// + Recover guard 拒绝 = 死胡同)。applied 后 Recover 放行,resume 门
|
|
1161
|
+
// 确定性重放。
|
|
1162
|
+
await markOwnerResolutionApplied({
|
|
1163
|
+
updateDiagnosticJson: (tid, json) => this.stateManager.updateTaskDiagnosticJson(tid, json),
|
|
1164
|
+
getTask: (tid) => this.stateManager.getTask(tid),
|
|
1165
|
+
taskId,
|
|
1166
|
+
resolutionId: resolution.resolutionId,
|
|
1167
|
+
appliedAt: new Date().toISOString(),
|
|
1168
|
+
});
|
|
1169
|
+
return effectResult.result;
|
|
1170
|
+
}
|
|
1171
|
+
await this.markCompletionIntentAppliedOrThrow(taskId);
|
|
1172
|
+
await markOwnerResolutionApplied({
|
|
1173
|
+
updateDiagnosticJson: (tid, json) => this.stateManager.updateTaskDiagnosticJson(tid, json),
|
|
1174
|
+
getTask: (tid) => this.stateManager.getTask(tid),
|
|
1175
|
+
taskId,
|
|
1176
|
+
resolutionId: resolution.resolutionId,
|
|
1177
|
+
appliedAt: new Date().toISOString(),
|
|
1178
|
+
});
|
|
1179
|
+
const resultRef = `${this.config.resultRefPrefix}://${resolution.sourceRunId}`;
|
|
1180
|
+
try {
|
|
1181
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
1182
|
+
}
|
|
1183
|
+
catch (stateErr) {
|
|
1184
|
+
this.emitEvent('mark_succeeded_failed', taskId, {
|
|
1185
|
+
taskId,
|
|
1186
|
+
runId: resolution.sourceRunId,
|
|
1187
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
1188
|
+
});
|
|
1189
|
+
throw stateErr;
|
|
1190
|
+
}
|
|
1191
|
+
this.emitEvent('task_succeeded', taskId, {
|
|
1192
|
+
attemptCount: leasedTask.attemptCount,
|
|
1193
|
+
resultRef,
|
|
1194
|
+
evaluationDecision: output.evaluation.decision,
|
|
1195
|
+
evaluationScore: output.evaluation.score,
|
|
1196
|
+
ruleArtifactId: effectResult.ruleArtifactId,
|
|
1197
|
+
ownerResolutionApplied: resolution.resolutionId,
|
|
1198
|
+
effectiveDecision: overrideDecision,
|
|
1199
|
+
});
|
|
1200
|
+
return {
|
|
1201
|
+
status: 'succeeded',
|
|
1202
|
+
taskId,
|
|
1203
|
+
runId: resolution.sourceRunId,
|
|
1204
|
+
artifactId,
|
|
1205
|
+
resultRef,
|
|
1206
|
+
contextHash,
|
|
1207
|
+
output,
|
|
1208
|
+
attemptCount: leasedTask.attemptCount,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
928
1211
|
/**
|
|
929
1212
|
* 从 runs 表恢复 intent 落库前已持久化的 validated output,并交叉核对
|
|
930
1213
|
* decision 与 intent 一致 (authority 记录一致性)。intent 的存在保证
|
|
@@ -985,7 +1268,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
985
1268
|
nextAction: 'owner_manual_review_required',
|
|
986
1269
|
priorRepairIteration,
|
|
987
1270
|
});
|
|
988
|
-
return { kind: 'max_iterations_reached' };
|
|
1271
|
+
return { kind: 'max_iterations_reached', detail: 'budget_exhausted' };
|
|
989
1272
|
}
|
|
990
1273
|
// ── Slice 4: seed artificer repair task ──
|
|
991
1274
|
// sourceArtificerArtifactId 由 caller 解析 (fresh: context ?? output;
|
|
@@ -997,7 +1280,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
997
1280
|
runId: evaluatorRunId,
|
|
998
1281
|
reason: 'source_artificer_artifact_id_unresolved',
|
|
999
1282
|
});
|
|
1000
|
-
return { kind: 'max_iterations_reached' };
|
|
1283
|
+
return { kind: 'max_iterations_reached', detail: 'seed_failed' };
|
|
1001
1284
|
}
|
|
1002
1285
|
// Construct the new repairPayload (repairIteration = prior + 1).
|
|
1003
1286
|
// The 6 fields are sourced from the current evaluator output (rc-7:
|
|
@@ -1048,7 +1331,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
1048
1331
|
runId: evaluatorRunId,
|
|
1049
1332
|
reason: 'seed_artificer_repair_task_not_injected',
|
|
1050
1333
|
});
|
|
1051
|
-
return { kind: 'max_iterations_reached' };
|
|
1334
|
+
return { kind: 'max_iterations_reached', detail: 'seed_failed' };
|
|
1052
1335
|
}
|
|
1053
1336
|
try {
|
|
1054
1337
|
const repairTaskId = await this.repairTaskSeeder({
|
|
@@ -1074,7 +1357,7 @@ export class EvaluatorRunner extends BasePeerRunner {
|
|
|
1074
1357
|
// Treat as max iterations reached so the caller skips markTaskSucceeded
|
|
1075
1358
|
// and returns succeeded — the evaluator verdict stands; only the repair
|
|
1076
1359
|
// seeding failed, which is logged.
|
|
1077
|
-
return { kind: 'max_iterations_reached' };
|
|
1360
|
+
return { kind: 'max_iterations_reached', detail: 'seed_failed' };
|
|
1078
1361
|
}
|
|
1079
1362
|
}
|
|
1080
1363
|
// ── Optional hooks ──────────────────────────────────────────────────────────
|