@principles/core 1.270.4 → 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.
Files changed (92) hide show
  1. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js +20 -0
  2. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
  3. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +26 -5
  4. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -1
  5. package/dist/runtime-v2/index.d.ts +7 -1
  6. package/dist/runtime-v2/index.d.ts.map +1 -1
  7. package/dist/runtime-v2/index.js +4 -0
  8. package/dist/runtime-v2/index.js.map +1 -1
  9. package/dist/runtime-v2/internalization/__tests__/evaluator-prompt-builder.test.js +1 -1
  10. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +15 -14
  11. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -1
  12. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts +2 -0
  13. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts.map +1 -0
  14. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js +56 -0
  15. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js.map +1 -0
  16. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts +2 -0
  17. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts.map +1 -0
  18. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js +883 -0
  19. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js.map +1 -0
  20. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts +2 -0
  21. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts.map +1 -0
  22. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js +328 -0
  23. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js.map +1 -0
  24. package/dist/runtime-v2/internalization/evaluator-output.d.ts +63 -2
  25. package/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  26. package/dist/runtime-v2/internalization/evaluator-output.js +139 -2
  27. package/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  28. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +54 -2
  29. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  30. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -2
  31. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  32. package/dist/runtime-v2/internalization/evaluator-runner.d.ts +34 -0
  33. package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  34. package/dist/runtime-v2/internalization/evaluator-runner.js +295 -12
  35. package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  36. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  37. package/dist/runtime-v2/internalization/internalization-orchestrator.js +10 -54
  38. package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  39. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +7 -1
  40. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -1
  41. package/dist/runtime-v2/internalization/internalization-transition-decision.js +9 -2
  42. package/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -1
  43. package/dist/runtime-v2/internalization/owner-resolution-service.d.ts +89 -0
  44. package/dist/runtime-v2/internalization/owner-resolution-service.d.ts.map +1 -0
  45. package/dist/runtime-v2/internalization/owner-resolution-service.js +272 -0
  46. package/dist/runtime-v2/internalization/owner-resolution-service.js.map +1 -0
  47. package/dist/runtime-v2/internalization/owner-retry.d.ts +12 -0
  48. package/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -1
  49. package/dist/runtime-v2/internalization/owner-retry.js +23 -0
  50. package/dist/runtime-v2/internalization/owner-retry.js.map +1 -1
  51. package/dist/runtime-v2/internalization/owner-review.d.ts +195 -0
  52. package/dist/runtime-v2/internalization/owner-review.d.ts.map +1 -0
  53. package/dist/runtime-v2/internalization/owner-review.js +470 -0
  54. package/dist/runtime-v2/internalization/owner-review.js.map +1 -0
  55. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +5 -1
  56. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  57. package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  58. package/dist/runtime-v2/internalization/pitask-metadata.d.ts +68 -0
  59. package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  60. package/dist/runtime-v2/internalization/pitask-metadata.js +104 -0
  61. package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  62. package/dist/runtime-v2/internalization/revision-reopen.d.ts +56 -0
  63. package/dist/runtime-v2/internalization/revision-reopen.d.ts.map +1 -0
  64. package/dist/runtime-v2/internalization/revision-reopen.js +144 -0
  65. package/dist/runtime-v2/internalization/revision-reopen.js.map +1 -0
  66. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +12 -0
  67. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
  68. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js +145 -14
  69. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
  70. package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js +1 -0
  71. package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js.map +1 -1
  72. package/dist/runtime-v2/store/runtime-state-manager.d.ts +6 -0
  73. package/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  74. package/dist/runtime-v2/store/runtime-state-manager.js +9 -0
  75. package/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  76. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts +2 -0
  77. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts.map +1 -0
  78. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js +78 -0
  79. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js.map +1 -0
  80. package/dist/runtime-v2/store/task/memory-task-store.d.ts +1 -0
  81. package/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
  82. package/dist/runtime-v2/store/task/memory-task-store.js +8 -0
  83. package/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
  84. package/dist/runtime-v2/store/task/sqlite-task-store.d.ts +1 -0
  85. package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
  86. package/dist/runtime-v2/store/task/sqlite-task-store.js +52 -0
  87. package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
  88. package/dist/runtime-v2/store/task/task-store.d.ts +9 -0
  89. package/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
  90. package/dist/runtime-v2/store/workspace-isolation.test.js +1 -0
  91. package/dist/runtime-v2/store/workspace-isolation.test.js.map +1 -1
  92. 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 { EvaluatorPromptBuilder } from './evaluator-prompt-builder.js';
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
- const result = await this.validator.validate(output, taskId, context.sourceArtificerArtifactId ?? undefined);
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 { decision } = finalOutput.evaluation;
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, 'repair_loop_max_iterations_or_seed_failure');
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: 'repair_loop_max_iterations_or_seed_failure',
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, runId, reason) {
848
+ async markNeedsHumanReviewOrThrow(taskId, review) {
849
+ const { runId, reasonCode, sourceArtifactId } = review;
715
850
  try {
716
- await this.stateManager.updateTask(taskId, { status: 'needs_human_review' });
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 ──────────────────────────────────────────────────────────