@principles/core 1.258.0 → 1.259.1

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 (170) hide show
  1. package/dist/runtime-v2/__tests__/architecture-regression.test.js +15 -2
  2. package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  3. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js +14 -6
  4. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
  5. package/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js +1 -0
  6. package/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js.map +1 -1
  7. package/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js +5 -0
  8. package/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js.map +1 -1
  9. package/dist/runtime-v2/__tests__/internalization-orchestrator.test.js +4 -1
  10. package/dist/runtime-v2/__tests__/internalization-orchestrator.test.js.map +1 -1
  11. package/dist/runtime-v2/__tests__/internalization-state-machine.test.js +3 -2
  12. package/dist/runtime-v2/__tests__/internalization-state-machine.test.js.map +1 -1
  13. package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js +47 -0
  14. package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js.map +1 -1
  15. package/dist/runtime-v2/__tests__/rollout-reviewer-runner-vslice.test.js +66 -1
  16. package/dist/runtime-v2/__tests__/rollout-reviewer-runner-vslice.test.js.map +1 -1
  17. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.d.ts +2 -0
  18. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.d.ts.map +1 -0
  19. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +234 -0
  20. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -0
  21. package/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js +23 -1
  22. package/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js.map +1 -1
  23. package/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +11 -0
  24. package/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -1
  25. package/dist/runtime-v2/activation/prompt-activation-reader-contract.js +11 -4
  26. package/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -1
  27. package/dist/runtime-v2/adapter/output-schema-registry.d.ts.map +1 -1
  28. package/dist/runtime-v2/adapter/output-schema-registry.js +2 -0
  29. package/dist/runtime-v2/adapter/output-schema-registry.js.map +1 -1
  30. package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +7 -2
  31. package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
  32. package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  33. package/dist/runtime-v2/feature-flags/feature-flag-contract.js +10 -6
  34. package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  35. package/dist/runtime-v2/feedback/__tests__/fingerprint.test.d.ts +2 -0
  36. package/dist/runtime-v2/feedback/__tests__/fingerprint.test.d.ts.map +1 -0
  37. package/dist/runtime-v2/feedback/__tests__/fingerprint.test.js +126 -0
  38. package/dist/runtime-v2/feedback/__tests__/fingerprint.test.js.map +1 -0
  39. package/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.d.ts +2 -0
  40. package/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.d.ts.map +1 -0
  41. package/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.js +278 -0
  42. package/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.js.map +1 -0
  43. package/dist/runtime-v2/feedback/create-report.d.ts.map +1 -1
  44. package/dist/runtime-v2/feedback/create-report.js +38 -1
  45. package/dist/runtime-v2/feedback/create-report.js.map +1 -1
  46. package/dist/runtime-v2/feedback/feedback-types.d.ts +60 -0
  47. package/dist/runtime-v2/feedback/feedback-types.d.ts.map +1 -1
  48. package/dist/runtime-v2/feedback/feedback-types.js +58 -0
  49. package/dist/runtime-v2/feedback/feedback-types.js.map +1 -1
  50. package/dist/runtime-v2/feedback/fingerprint.d.ts +31 -0
  51. package/dist/runtime-v2/feedback/fingerprint.d.ts.map +1 -0
  52. package/dist/runtime-v2/feedback/fingerprint.js +51 -0
  53. package/dist/runtime-v2/feedback/fingerprint.js.map +1 -0
  54. package/dist/runtime-v2/feedback/index.d.ts +3 -2
  55. package/dist/runtime-v2/feedback/index.d.ts.map +1 -1
  56. package/dist/runtime-v2/feedback/index.js +2 -1
  57. package/dist/runtime-v2/feedback/index.js.map +1 -1
  58. package/dist/runtime-v2/feedback/privacy-preview.d.ts.map +1 -1
  59. package/dist/runtime-v2/feedback/privacy-preview.js +43 -0
  60. package/dist/runtime-v2/feedback/privacy-preview.js.map +1 -1
  61. package/dist/runtime-v2/feedback/render-markdown.d.ts.map +1 -1
  62. package/dist/runtime-v2/feedback/render-markdown.js +33 -0
  63. package/dist/runtime-v2/feedback/render-markdown.js.map +1 -1
  64. package/dist/runtime-v2/index.d.ts +11 -7
  65. package/dist/runtime-v2/index.d.ts.map +1 -1
  66. package/dist/runtime-v2/index.js +5 -3
  67. package/dist/runtime-v2/index.js.map +1 -1
  68. package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.d.ts +2 -0
  69. package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.d.ts.map +1 -0
  70. package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.js +157 -0
  71. package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.js.map +1 -0
  72. package/dist/runtime-v2/internalization/__tests__/c2-live-runner-chain.test.js +29 -0
  73. package/dist/runtime-v2/internalization/__tests__/c2-live-runner-chain.test.js.map +1 -1
  74. package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.d.ts +2 -0
  75. package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.d.ts.map +1 -0
  76. package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.js +311 -0
  77. package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.js.map +1 -0
  78. package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.d.ts +2 -0
  79. package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.d.ts.map +1 -0
  80. package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js +171 -0
  81. package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js.map +1 -0
  82. package/dist/runtime-v2/internalization/__tests__/internalization-task-guards.test.js +11 -2
  83. package/dist/runtime-v2/internalization/__tests__/internalization-task-guards.test.js.map +1 -1
  84. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.d.ts +2 -0
  85. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.d.ts.map +1 -0
  86. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +335 -0
  87. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -0
  88. package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.d.ts +2 -0
  89. package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.d.ts.map +1 -0
  90. package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.js +515 -0
  91. package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.js.map +1 -0
  92. package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.d.ts +2 -0
  93. package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.d.ts.map +1 -0
  94. package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.js +167 -0
  95. package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.js.map +1 -0
  96. package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.d.ts +2 -0
  97. package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.d.ts.map +1 -0
  98. package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.js +601 -0
  99. package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.js.map +1 -0
  100. package/dist/runtime-v2/internalization/artificer-runner.d.ts +7 -0
  101. package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
  102. package/dist/runtime-v2/internalization/artificer-runner.js +18 -2
  103. package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
  104. package/dist/runtime-v2/internalization/evaluator-runner.d.ts +49 -0
  105. package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  106. package/dist/runtime-v2/internalization/evaluator-runner.js +390 -98
  107. package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  108. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +127 -0
  109. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  110. package/dist/runtime-v2/internalization/internalization-orchestrator.js +345 -1
  111. package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  112. package/dist/runtime-v2/internalization/internalization-task-guards.d.ts +9 -2
  113. package/dist/runtime-v2/internalization/internalization-task-guards.d.ts.map +1 -1
  114. package/dist/runtime-v2/internalization/internalization-task-guards.js +14 -3
  115. package/dist/runtime-v2/internalization/internalization-task-guards.js.map +1 -1
  116. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +61 -0
  117. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -0
  118. package/dist/runtime-v2/internalization/internalization-transition-decision.js +84 -0
  119. package/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -0
  120. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +17 -1
  121. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  122. package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  123. package/dist/runtime-v2/internalization/pitask-metadata.d.ts +99 -2
  124. package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  125. package/dist/runtime-v2/internalization/pitask-metadata.js +148 -0
  126. package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  127. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +147 -0
  128. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
  129. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js +672 -1
  130. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
  131. package/dist/runtime-v2/internalization/scribe-runner.d.ts +2 -0
  132. package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  133. package/dist/runtime-v2/internalization/scribe-runner.js +21 -1
  134. package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  135. package/dist/runtime-v2/runner/base-peer-runner.d.ts +9 -0
  136. package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
  137. package/dist/runtime-v2/runner/base-peer-runner.js +22 -0
  138. package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
  139. package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.d.ts +2 -0
  140. package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.d.ts.map +1 -0
  141. package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.js +130 -0
  142. package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.js.map +1 -0
  143. package/dist/runtime-v2/signal-collector/index.d.ts +1 -1
  144. package/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
  145. package/dist/runtime-v2/signal-collector/index.js +1 -1
  146. package/dist/runtime-v2/signal-collector/index.js.map +1 -1
  147. package/dist/runtime-v2/signal-collector/llm-stage.d.ts +15 -0
  148. package/dist/runtime-v2/signal-collector/llm-stage.d.ts.map +1 -1
  149. package/dist/runtime-v2/signal-collector/llm-stage.js +28 -0
  150. package/dist/runtime-v2/signal-collector/llm-stage.js.map +1 -1
  151. package/dist/runtime-v2/signal-collector/types.d.ts +9 -1
  152. package/dist/runtime-v2/signal-collector/types.d.ts.map +1 -1
  153. package/dist/runtime-v2/signal-collector/types.js +13 -1
  154. package/dist/runtime-v2/signal-collector/types.js.map +1 -1
  155. package/dist/runtime-v2/store/reconciliation-cursor-store.d.ts +23 -0
  156. package/dist/runtime-v2/store/reconciliation-cursor-store.d.ts.map +1 -0
  157. package/dist/runtime-v2/store/reconciliation-cursor-store.js +34 -0
  158. package/dist/runtime-v2/store/reconciliation-cursor-store.js.map +1 -0
  159. package/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  160. package/dist/runtime-v2/store/sqlite-connection.js +7 -0
  161. package/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  162. package/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
  163. package/dist/runtime-v2/store/task/memory-task-store.js +22 -0
  164. package/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
  165. package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
  166. package/dist/runtime-v2/store/task/sqlite-task-store.js +20 -0
  167. package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
  168. package/dist/runtime-v2/store/task/task-store.d.ts +14 -0
  169. package/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
  170. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { isEvaluatorOutputV2 } from './evaluator-output.js';
2
2
  import { PDRuntimeError, isPDErrorCategory } from '../error-categories.js';
3
- import { hydratePITaskRecord } from './pitask-metadata.js';
3
+ import { hydratePITaskRecord, createPITaskDiagnosticJson, mergePITaskMetadata } from './pitask-metadata.js';
4
4
  import { EvaluatorPromptBuilder } from './evaluator-prompt-builder.js';
5
5
  import { reconcileLineageEcho } from './peer-runner-contracts.js';
6
6
  import { BasePeerRunner } from '../runner/base-peer-runner.js';
@@ -504,34 +504,85 @@ export class EvaluatorRunner extends BasePeerRunner {
504
504
  }
505
505
  }
506
506
  }
507
- // ── PRI-427: rule artifact assembly ──
508
- // When the evaluator output is V2 AND the adversarial replay passed, write
509
- // a second artifact with artifactKind='rule' carrying the executable code
510
- // + golden trace + gate decision, then mark it 'validated' so the downstream
511
- // RuleHostWriter.canActivate path accepts it.
512
- //
513
- // PRD Decision 5 contract:
514
- // - rule artifact goldenTrace = Artificer's FULL trace (buildGoldenTraceFromArtificer),
515
- // NOT the adversarial replay trace. The adversarial trace was only used
516
- // to TEST the code in PRI-426; enforcement uses the production trace.
517
- // - ruleHostGateDecision must be 'accepted_shadow' for RuleHostWriter to
518
- // accept (rule-host-writer.ts extractRuleHostGateDecision).
519
- // - Assembly failure is non-fatal: principle artifact is already written,
520
- // prompt-channel fallback remains available (PRD Decision 5 degradation).
521
- let ruleArtifactId = null;
522
- if (isEvaluatorOutputV2(finalOutput) && finalOutput.adversarialResult?.passed === true) {
523
- ruleArtifactId = await this.assembleRuleArtifact(finalOutput, taskId, runId, context, lineageArtifactIds);
507
+ // ── P0 (verdict drift): verdict + completion intent 原子落库 ──
508
+ // 必须先于一切治理 side effect (validate bearer / seed repair / rule
509
+ // assembly):side effect 已发生而 intent 未落 = crash 后重跑会重新问
510
+ // LLM,新 verdict 与已发生副作用形成治理矛盾 (repair drift /
511
+ // validation drift / validated-rule drift)。同 epoch crash/retry 重跑经
512
+ // maybeResumePendingIntent resume,不重问。
513
+ await this.recordCompletionOrThrow(taskId, runId, finalOutput.evaluation.decision);
514
+ const ruleAssemblyInput = {
515
+ artificerArtifact: context.artificerArtifact ?? null,
516
+ sourceArtificerArtifactId: context.sourceArtificerArtifactId ?? null,
517
+ };
518
+ const sourceArtificerArtifactId = context.sourceArtificerArtifactId
519
+ ?? finalOutput.sourceArtificerArtifactId
520
+ ?? null;
521
+ const effectResult = await this.applyEvaluatorDecisionEffects({
522
+ taskId, runId, finalOutput, task, artifactId, contextHash, sourceArtificerArtifactId, ruleAssemblyInput,
523
+ });
524
+ if (effectResult.kind === 'human_review') {
525
+ return effectResult.result;
526
+ }
527
+ // ── P0 invariant 5: intent APPLIED 后才允许 terminal ──
528
+ await this.markCompletionIntentAppliedOrThrow(taskId);
529
+ const resultRef = `${this.config.resultRefPrefix}://${runId}`;
530
+ try {
531
+ await this.stateManager.markTaskSucceeded(taskId, resultRef);
524
532
  }
533
+ catch (stateErr) {
534
+ this.emitEvent('mark_succeeded_failed', taskId, {
535
+ taskId,
536
+ runId,
537
+ errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
538
+ });
539
+ throw stateErr;
540
+ }
541
+ this.emitEvent('task_succeeded', taskId, {
542
+ attemptCount: task.attemptCount,
543
+ resultRef,
544
+ evaluationDecision: finalOutput.evaluation.decision,
545
+ evaluationScore: finalOutput.evaluation.score,
546
+ ruleArtifactId: effectResult.ruleArtifactId,
547
+ });
548
+ return {
549
+ status: 'succeeded',
550
+ taskId,
551
+ runId,
552
+ artifactId,
553
+ resultRef,
554
+ contextHash,
555
+ output: finalOutput,
556
+ attemptCount: task.attemptCount,
557
+ };
558
+ }
559
+ /**
560
+ * P0 (verdict drift): 执行 decision 的治理效果 (fresh 与 resume 共用,
561
+ * 幂等): approved → validate principle bearer;needs_revision (repair loop
562
+ * on) → deterministic repair seed / max-iterations needs_human_review;
563
+ * rejected → 无效果。
564
+ *
565
+ * 返回非 null = terminal 结果 (needs_human_review 族,caller 直接返回,
566
+ * 不得 markTaskSucceeded);null = effects 完成,caller 标 intent applied
567
+ * 后 markSucceeded。
568
+ */
569
+ async applyEvaluatorDecisionEffects(args) {
570
+ const { taskId, runId, finalOutput, task, artifactId, contextHash, sourceArtificerArtifactId } = args;
571
+ const { decision } = finalOutput.evaluation;
525
572
  // ── Evaluator-specific: validate principle-bearing Scribe artifact ──
526
573
  // This is the critical business logic: approved evaluator must validate
527
574
  // the Scribe principle artifact, NOT the Artificer plan artifact.
528
- if (output.evaluation.decision === 'approved') {
529
- const principleArtifactId = await this.resolvePrincipleBearerArtifact(output, taskId);
575
+ // Idempotent: repeated updateValidationStatus('validated') is safe —
576
+ // crash-resume re-applies without contradiction.
577
+ if (decision === 'approved') {
578
+ const principleArtifactId = await this.resolvePrincipleBearerArtifact(finalOutput, taskId);
530
579
  if (principleArtifactId) {
531
580
  try {
532
581
  const updated = await this.artifactStore.updateValidationStatus(principleArtifactId, 'validated');
533
582
  if (!updated) {
534
- // updateValidationStatus returned false — fail loud with structured telemetry (ERR-018)
583
+ // updateValidationStatus returned false — deterministic store
584
+ // inconsistency (bearer 不在 store);结构化降级 (telemetry +
585
+ // rollout 侧 resolveActivationCandidate 兜底 needs_human_review)。
535
586
  this.emitEvent('source_validation_update_not_found', taskId, {
536
587
  runId,
537
588
  sourceArtifactId: principleArtifactId,
@@ -545,47 +596,50 @@ export class EvaluatorRunner extends BasePeerRunner {
545
596
  runId,
546
597
  sourceArtifactId: principleArtifactId,
547
598
  errorMessage: updateErr instanceof Error ? updateErr.message : String(updateErr),
599
+ nextAction: 'task_will_retry; repeated validated write is idempotent',
548
600
  });
601
+ // P0 (INV-2): bearer validated 是 approved 的 required effect —
602
+ // 存储写失败必须重试 (resume 幂等重放),不得吞掉后标 intent applied。
603
+ throw updateErr;
549
604
  }
550
605
  }
606
+ // ── P0-B (verdict drift 完整性): rule assembly 是治理 side effect ──
607
+ // validated rule 会被 RuleHostWriter.canActivate 消费,必须在 durable
608
+ // completion intent 之后执行 (原顺序: assembly 在 intent 前 → crash 后
609
+ // 新 verdict 可与已 validated rule 冲突)。fresh 与 resume 共用本路径;
610
+ // resume 的 assembly 输入由 durable lineage 重建 (store 按
611
+ // sourceArtificerArtifactId 取 contentJson),deterministic
612
+ // pi-rule-<taskId>-<runId> 保证重放不重复。
613
+ if (isEvaluatorOutputV2(finalOutput) && finalOutput.adversarialResult?.passed === true) {
614
+ let lineageIds = [];
615
+ try {
616
+ lineageIds = (await this.resolveLineageArtifactIds(taskId)).ids;
617
+ }
618
+ catch (lineageErr) {
619
+ this.emitEvent('lineage_resolve_failed', taskId, {
620
+ runId,
621
+ errorMessage: lineageErr instanceof Error ? lineageErr.message : String(lineageErr),
622
+ });
623
+ }
624
+ const ruleArtifactId = await this.assembleRuleArtifact(finalOutput, taskId, runId, args.ruleAssemblyInput, lineageIds);
625
+ return { kind: 'completed', ruleArtifactId };
626
+ }
627
+ return { kind: 'completed', ruleArtifactId: null };
551
628
  }
552
629
  // ── PRI-509: Evaluator→Artificer Repair Loop ──
553
- // When decision === 'needs_revision' AND the feature flag is enabled:
554
- // 1. Resolve priorRepairIteration from the dependency artificer task's
555
- // repairPayload (rc-7: read at task creation, never inferred).
556
- // 2. If priorRepairIteration >= 2 (max 2 rounds reached):
557
- // - Mark the evaluator task needs_human_review (fail loud, EP-03/ERR-002).
558
- // - Emit a structured repair_loop_max_iterations event with reason +
559
- // nextAction (rc-9: no silent fallback).
560
- // - Return succeeded (the evaluator produced a valid verdict; the task
561
- // status reflects the need for human review, not a runner failure).
562
- // 3. Else (priorRepairIteration < 2):
563
- // - Seed a new artificer repair task carrying a fresh repairPayload
564
- // (repairIteration = priorRepairIteration + 1) so the artificer can
565
- // address the evaluator's structured feedback.
566
- // - Fall through to the normal markTaskSucceeded path.
567
- if (output.evaluation.decision === 'needs_revision' && this.isRepairLoopEnabled()) {
568
- const repairOutcome = await this.maybeSeedArtificerRepair(taskId, { runId, output, context });
630
+ // needs_revision repair loop 开启: seed 确定性 ID repair 任务
631
+ // (幂等,crash-resume 不重复);max iterations (2) needs_human_review。
632
+ if (decision === 'needs_revision' && this.isRepairLoopEnabled()) {
633
+ const repairOutcome = await this.maybeSeedArtificerRepair(taskId, { runId, output: finalOutput, sourceArtificerArtifactId });
569
634
  if (repairOutcome.kind === 'max_iterations_reached') {
570
635
  // Fail loud (rc-9, EP-03, ERR-002): mark the task needs_human_review
571
636
  // so it does NOT stay in 'leased' state (which would cause the lease
572
637
  // to expire and the evaluator to re-run the same verdict infinitely).
573
- //
574
- // The runner result remains 'succeeded' the evaluator produced a
575
- // valid verdict; the *task status* reflects that human review is
576
- // required, not that the runner failed.
638
+ // P0 (INV-2): needs_human_review 是本 completion 的 materialize 操作 —
639
+ // fail-closed: 写失败 throw retry_wait 入口门 resume 同一效果,
640
+ // 不问 LLM;禁止吞错后继续 (intent applied effect durable)。
577
641
  const resultRef = `${this.config.resultRefPrefix}://${runId}`;
578
- try {
579
- await this.stateManager.updateTask(taskId, { status: 'needs_human_review' });
580
- }
581
- catch (stateErr) {
582
- // Surface the state-update failure observably (rc-9 — never silent).
583
- this.emitEvent('repair_loop_mark_review_failed', taskId, {
584
- runId,
585
- errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
586
- nextAction: 'manual_intervention_required',
587
- });
588
- }
642
+ await this.markNeedsHumanReviewOrThrow(taskId, runId, 'repair_loop_max_iterations_or_seed_failure');
589
643
  this.emitEvent('task_needs_human_review', taskId, {
590
644
  attemptCount: task.attemptCount,
591
645
  resultRef,
@@ -595,48 +649,24 @@ export class EvaluatorRunner extends BasePeerRunner {
595
649
  reason: 'repair_loop_max_iterations_or_seed_failure',
596
650
  });
597
651
  return {
598
- status: 'succeeded',
599
- taskId,
600
- runId,
601
- artifactId,
602
- resultRef,
603
- contextHash,
604
- output: finalOutput,
605
- attemptCount: task.attemptCount,
652
+ kind: 'human_review',
653
+ result: {
654
+ status: 'succeeded',
655
+ taskId,
656
+ runId,
657
+ artifactId,
658
+ resultRef,
659
+ contextHash,
660
+ output: finalOutput,
661
+ attemptCount: task.attemptCount,
662
+ },
606
663
  };
607
664
  }
608
- // repairOutcome.kind === 'repair_seeded' → fall through to markTaskSucceeded.
665
+ // repairOutcome.kind === 'repair_seeded' → fall through (completed)
609
666
  }
610
- // Mark task succeeded
611
- const resultRef = `${this.config.resultRefPrefix}://${runId}`;
612
- try {
613
- await this.stateManager.markTaskSucceeded(taskId, resultRef);
614
- }
615
- catch (stateErr) {
616
- this.emitEvent('mark_succeeded_failed', taskId, {
617
- taskId,
618
- runId,
619
- errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
620
- });
621
- throw stateErr;
622
- }
623
- this.emitEvent('task_succeeded', taskId, {
624
- attemptCount: task.attemptCount,
625
- resultRef,
626
- evaluationDecision: finalOutput.evaluation.decision,
627
- evaluationScore: finalOutput.evaluation.score,
628
- ruleArtifactId,
629
- });
630
- return {
631
- status: 'succeeded',
632
- taskId,
633
- runId,
634
- artifactId,
635
- resultRef,
636
- contextHash,
637
- output: finalOutput,
638
- attemptCount: task.attemptCount,
639
- };
667
+ // rejected / needs_revision (repair loop off): 无治理效果 — commit 门的
668
+ // transition decision 依据 durable runnerDecision fail-closed。
669
+ return { kind: 'completed', ruleArtifactId: null };
640
670
  }
641
671
  /**
642
672
  * PRI-509: Resolve the prior repair iteration by reading the dependency
@@ -669,6 +699,266 @@ export class EvaluatorRunner extends BasePeerRunner {
669
699
  }
670
700
  return 0;
671
701
  }
702
+ /**
703
+ * 把 runner verdict 持久化进任务 diagnosticJson(commit 门控的输入)。
704
+ * 失败不静默 (rc-9): emitEvent 后吞掉 — verdict 已在 events/runs 中可观测,
705
+ * 且 commit 门对缺失 verdict 走 legacy 推进,不会因记录失败而卡链。
706
+ */
707
+ // ── P0 (verdict drift): completion intent — record / applied / resume ──
708
+ /**
709
+ * P0 (INV-2): needs_human_review 是 completion effect 的 materialize 操作 —
710
+ * fail-closed + read-back。写失败 throw → retry_wait → 入口门 resume 同一
711
+ * effect,不问 LLM;禁止吞错后让 caller 标 intent applied
712
+ * (intent applied ⇔ 其 durable effect 已 materialize)。
713
+ */
714
+ async markNeedsHumanReviewOrThrow(taskId, runId, reason) {
715
+ try {
716
+ await this.stateManager.updateTask(taskId, { status: 'needs_human_review' });
717
+ // read-back invariant (INV-2): 只有 effect durable 才允许 caller 标 applied
718
+ const current = await this.stateManager.getTask(taskId);
719
+ if (!current || current.status !== 'needs_human_review') {
720
+ throw new PDRuntimeError('storage_unavailable', `needs_human_review effect not durable for task ${taskId} (read-back status: ${current?.status ?? 'task_missing'})`);
721
+ }
722
+ }
723
+ catch (err) {
724
+ this.emitEvent('repair_loop_mark_review_failed', taskId, {
725
+ runId,
726
+ reason,
727
+ errorMessage: err instanceof Error ? err.message : String(err),
728
+ nextAction: 'task_will_retry_then_resume_completion_intent_without_llm',
729
+ });
730
+ throw err;
731
+ }
732
+ }
733
+ /**
734
+ * verdict + completion intent 原子落库 (单次 metadata 写)。intent 的存在
735
+ * 证明 output 已 durable (updateRunOutput 在 succeedTask 最前)。
736
+ * 同 epoch crash/retry 重跑经 maybeResumePendingIntent resume,不重问 LLM。
737
+ */
738
+ async recordCompletionOrThrow(taskId, runId, decision) {
739
+ try {
740
+ const raw = await this.stateManager.getTask(taskId);
741
+ if (!raw)
742
+ throw new Error(`task ${taskId} not found`);
743
+ const piTask = hydratePITaskRecord(raw);
744
+ if (!piTask)
745
+ throw new Error(`task ${taskId} not hydratable`);
746
+ const merged = mergePITaskMetadata(piTask, {
747
+ runnerDecision: decision === 'approved' || decision === 'needs_revision' || decision === 'rejected'
748
+ ? decision
749
+ : piTask.runnerDecision,
750
+ completionIntent: {
751
+ decision: decision,
752
+ sourceRunId: runId,
753
+ revisionEpoch: piTask.revisionCount ?? 0,
754
+ status: 'pending',
755
+ },
756
+ });
757
+ await this.stateManager.updateTaskDiagnosticJson(taskId, createPITaskDiagnosticJson(merged));
758
+ }
759
+ catch (err) {
760
+ // P0-3 (外部复核): 吞掉写失败 = succeeded 任务无 durable verdict,
761
+ // commit 门退化为不可判定 — fail loud,由重试机制重写 (verdict 仍在 runs)。
762
+ this.emitEvent('completion_record_failed', taskId, {
763
+ errorMessage: err instanceof Error ? err.message : String(err),
764
+ decision,
765
+ nextAction: 'task_will_retry; record is idempotent overwrite',
766
+ });
767
+ throw err;
768
+ }
769
+ }
770
+ /** intent APPLIED 后才允许 terminal (P0 invariant 5)。写失败 fail loud。 */
771
+ async markCompletionIntentAppliedOrThrow(taskId) {
772
+ try {
773
+ const raw = await this.stateManager.getTask(taskId);
774
+ if (!raw)
775
+ throw new Error(`task ${taskId} not found`);
776
+ const piTask = hydratePITaskRecord(raw);
777
+ if (!piTask?.completionIntent)
778
+ return;
779
+ const merged = mergePITaskMetadata(piTask, {
780
+ completionIntent: { ...piTask.completionIntent, status: 'applied' },
781
+ });
782
+ await this.stateManager.updateTaskDiagnosticJson(taskId, createPITaskDiagnosticJson(merged));
783
+ }
784
+ catch (err) {
785
+ this.emitEvent('completion_mark_applied_failed', taskId, {
786
+ errorMessage: err instanceof Error ? err.message : String(err),
787
+ nextAction: 'task_will_retry; effects are idempotent on resume',
788
+ });
789
+ throw err;
790
+ }
791
+ }
792
+ /**
793
+ * 入口恢复门 (BasePeerRunner hook): pending completion intent (同 epoch)
794
+ * 是 recovery authority。返回非 null = 本次 run 以 resume 完成 (LLM 未被
795
+ * 调用);返回 null = 走正常 LLM 管线。真正的 revision reopen 已清空
796
+ * intent (新 epoch 允许新 verdict);epoch 不匹配的残留视为 stale。
797
+ */
798
+ async maybeResumePendingIntent(taskId, leasedTask) {
799
+ const piTask = hydratePITaskRecord(leasedTask);
800
+ const intent = piTask?.completionIntent;
801
+ if (!piTask || !intent || intent.status !== 'pending') {
802
+ // P0 (INV-1/INV-5): applied 但任务未 terminal (标 applied 后、
803
+ // markTaskSucceeded 写失败/crash 的窗口) — effects 已 materialize,
804
+ // 不重问 LLM,直接补 terminal。
805
+ if (piTask && intent && intent.status === 'applied'
806
+ && intent.revisionEpoch === (piTask.revisionCount ?? 0)
807
+ && leasedTask.status !== 'needs_human_review') {
808
+ return await this.finalizeAppliedIntentTerminal({ taskId, decision: intent.decision, sourceRunId: intent.sourceRunId, leasedTask });
809
+ }
810
+ return null;
811
+ }
812
+ if (intent.revisionEpoch !== (piTask.revisionCount ?? 0)) {
813
+ this.emitEvent('completion_intent_stale_epoch', taskId, {
814
+ intentEpoch: intent.revisionEpoch,
815
+ currentEpoch: piTask.revisionCount ?? 0,
816
+ nextAction: 'reopen should have cleared intent; verify reopenTaskForRevision path',
817
+ });
818
+ return null;
819
+ }
820
+ this.emitEvent('completion_intent_resumed', taskId, {
821
+ decision: intent.decision,
822
+ sourceRunId: intent.sourceRunId,
823
+ });
824
+ const output = await this.recoverIntentOutput(taskId, intent.sourceRunId, intent.decision);
825
+ const artifactId = `pi-art-${taskId}-${intent.sourceRunId}`;
826
+ const contextHash = `resume-${intent.sourceRunId}`;
827
+ // P0-B: rule assembly 输入由 durable lineage 重建 (fresh 路径来自内存
828
+ // context;resume 不得依赖瞬时内存) — store 按 output 的
829
+ // sourceArtificerArtifactId 取 artificer contentJson。
830
+ const assemblySourceId = output.sourceArtificerArtifactId ?? null;
831
+ let artificerContent = null;
832
+ if (assemblySourceId) {
833
+ try {
834
+ const rec = await this.artifactStore.getArtifactById(assemblySourceId);
835
+ artificerContent = rec?.contentJson ?? null;
836
+ }
837
+ catch {
838
+ artificerContent = null; // assembly 将结构化降级 (rule_assembly_failed)
839
+ }
840
+ }
841
+ const effectResult = await this.applyEvaluatorDecisionEffects({
842
+ taskId,
843
+ runId: intent.sourceRunId,
844
+ finalOutput: output,
845
+ task: leasedTask,
846
+ artifactId,
847
+ contextHash,
848
+ sourceArtificerArtifactId: assemblySourceId,
849
+ ruleAssemblyInput: { artificerArtifact: artificerContent, sourceArtificerArtifactId: assemblySourceId },
850
+ });
851
+ if (effectResult.kind === 'human_review') {
852
+ return effectResult.result;
853
+ }
854
+ await this.markCompletionIntentAppliedOrThrow(taskId);
855
+ const resultRef = `${this.config.resultRefPrefix}://${intent.sourceRunId}`;
856
+ try {
857
+ await this.stateManager.markTaskSucceeded(taskId, resultRef);
858
+ }
859
+ catch (stateErr) {
860
+ this.emitEvent('mark_succeeded_failed', taskId, {
861
+ taskId,
862
+ runId: intent.sourceRunId,
863
+ errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
864
+ });
865
+ throw stateErr;
866
+ }
867
+ this.emitEvent('task_succeeded', taskId, {
868
+ attemptCount: leasedTask.attemptCount,
869
+ resultRef,
870
+ evaluationDecision: output.evaluation.decision,
871
+ evaluationScore: output.evaluation.score,
872
+ ruleArtifactId: effectResult.ruleArtifactId,
873
+ resumedFromCompletionIntent: true,
874
+ });
875
+ return {
876
+ status: 'succeeded',
877
+ taskId,
878
+ runId: intent.sourceRunId,
879
+ artifactId,
880
+ resultRef,
881
+ contextHash,
882
+ output,
883
+ attemptCount: leasedTask.attemptCount,
884
+ };
885
+ }
886
+ /**
887
+ * P0 (INV-1/INV-5): applied intent 的补 terminal — effects 已 materialize
888
+ * (applied ⇒ INV-2 保证),仅 markTaskSucceeded 缺失。不调用 LLM。
889
+ */
890
+ async finalizeAppliedIntentTerminal(args) {
891
+ const { taskId, decision, sourceRunId, leasedTask } = args;
892
+ this.emitEvent('completion_intent_finalize_terminal', taskId, {
893
+ sourceRunId,
894
+ taskStatus: leasedTask.status,
895
+ });
896
+ const output = await this.recoverIntentOutput(taskId, sourceRunId, decision);
897
+ const resultRef = `${this.config.resultRefPrefix}://${sourceRunId}`;
898
+ try {
899
+ await this.stateManager.markTaskSucceeded(taskId, resultRef);
900
+ }
901
+ catch (stateErr) {
902
+ this.emitEvent('mark_succeeded_failed', taskId, {
903
+ taskId,
904
+ runId: sourceRunId,
905
+ errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
906
+ });
907
+ throw stateErr;
908
+ }
909
+ this.emitEvent('task_succeeded', taskId, {
910
+ attemptCount: leasedTask.attemptCount,
911
+ resultRef,
912
+ evaluationDecision: output.evaluation.decision,
913
+ evaluationScore: output.evaluation.score,
914
+ ruleArtifactId: null,
915
+ finalizedFromAppliedIntent: true,
916
+ });
917
+ return {
918
+ status: 'succeeded',
919
+ taskId,
920
+ runId: sourceRunId,
921
+ artifactId: `pi-art-${taskId}-${sourceRunId}`,
922
+ resultRef,
923
+ contextHash: `resume-${sourceRunId}`,
924
+ output,
925
+ attemptCount: leasedTask.attemptCount,
926
+ };
927
+ }
928
+ /**
929
+ * 从 runs 表恢复 intent 落库前已持久化的 validated output,并交叉核对
930
+ * decision 与 intent 一致 (authority 记录一致性)。intent 的存在保证
931
+ * updateRunOutput 曾成功;缺失/损坏/漂移 = 存储腐坏 → fail loud。
932
+ */
933
+ async recoverIntentOutput(taskId, sourceRunId, expectedDecision) {
934
+ const runs = await this.stateManager.getRunsByTask(taskId);
935
+ const run = runs.find((r) => r.runId === sourceRunId);
936
+ const raw = run?.outputPayload;
937
+ if (typeof raw !== 'string' || raw.length === 0) {
938
+ throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} has no outputPayload`);
939
+ }
940
+ let parsed;
941
+ try {
942
+ parsed = JSON.parse(raw);
943
+ }
944
+ catch {
945
+ throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} has unparseable outputPayload`);
946
+ }
947
+ if (typeof parsed !== 'object' || parsed === null) {
948
+ throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} payload is not an object`);
949
+ }
950
+ // runtime-contract-exempt: ERR-001 field access on object-guarded unknown; the decision value is typeof-checked immediately below and must equal the durable intent's decision
951
+ const { evaluation } = parsed;
952
+ const decision = typeof evaluation === 'object' && evaluation !== null
953
+ // runtime-contract-exempt: ERR-001 same object-guarded field access pattern as above; value compared against the expected decision, never trusted
954
+ ? evaluation.decision
955
+ : undefined;
956
+ if (decision !== expectedDecision) {
957
+ throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} decision '${String(decision)}' does not match intent '${expectedDecision}'`);
958
+ }
959
+ // runtime-contract-exempt: ERR-001 output passed the full validate pipeline before persistence (run() trust boundary); this cast only narrows stored-not-fresh data whose decision was cross-checked above
960
+ return parsed;
961
+ }
672
962
  /**
673
963
  * PRI-509: Seed an artificer repair task or mark the evaluator task
674
964
  * needs_human_review when max iterations (2) are reached.
@@ -683,7 +973,7 @@ export class EvaluatorRunner extends BasePeerRunner {
683
973
  * re-validation is not required here (rc-4 N/A — not unknown at this point).
684
974
  */
685
975
  async maybeSeedArtificerRepair(evaluatorTaskId, ctx) {
686
- const { runId: evaluatorRunId, output, context } = ctx;
976
+ const { runId: evaluatorRunId, output } = ctx;
687
977
  const priorRepairIteration = await this.resolvePriorRepairIteration(evaluatorTaskId);
688
978
  // ── Slice 5: max iterations (2) reached → fail loud ──
689
979
  if (priorRepairIteration >= 2) {
@@ -698,7 +988,9 @@ export class EvaluatorRunner extends BasePeerRunner {
698
988
  return { kind: 'max_iterations_reached' };
699
989
  }
700
990
  // ── Slice 4: seed artificer repair task ──
701
- const sourceArtificerArtifactId = context.sourceArtificerArtifactId ?? output.sourceArtificerArtifactId;
991
+ // sourceArtificerArtifactId caller 解析 (fresh: context ?? output;
992
+ // resume: output — intent 落库前 output 已 durable)。
993
+ const sourceArtificerArtifactId = ctx.sourceArtificerArtifactId ?? output.sourceArtificerArtifactId;
702
994
  if (!sourceArtificerArtifactId) {
703
995
  // Lineage missing — cannot construct repairPayload. Fail loud (rc-3).
704
996
  this.emitEvent('repair_loop_lineage_missing', evaluatorTaskId, {
@@ -1187,8 +1479,8 @@ export class EvaluatorRunner extends BasePeerRunner {
1187
1479
  * emits structured telemetry with a reason (Runtime Rule 9, ERR-018).
1188
1480
  */
1189
1481
  // eslint-disable-next-line @typescript-eslint/max-params
1190
- async assembleRuleArtifact(output, taskId, runId, context, lineageArtifactIds) {
1191
- if (!context.artificerArtifact) {
1482
+ async assembleRuleArtifact(output, taskId, runId, assemblyInput, lineageArtifactIds) {
1483
+ if (!assemblyInput.artificerArtifact) {
1192
1484
  this.emitEvent('rule_assembly_failed', taskId, {
1193
1485
  runId,
1194
1486
  reason: 'no_artificer_artifact_in_context',
@@ -1196,7 +1488,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1196
1488
  });
1197
1489
  return null;
1198
1490
  }
1199
- const artificerParsed = this.parseArtificerArtifact(context.artificerArtifact);
1491
+ const artificerParsed = this.parseArtificerArtifact(assemblyInput.artificerArtifact);
1200
1492
  if (!artificerParsed) {
1201
1493
  this.emitEvent('rule_assembly_failed', taskId, {
1202
1494
  runId,
@@ -1232,7 +1524,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1232
1524
  // + ≥1 negative case.
1233
1525
  const traceBuild = buildGoldenTraceFromArtificer({
1234
1526
  cases: goldenTraceCases,
1235
- sourceArtifactId: context.sourceArtificerArtifactId ?? undefined,
1527
+ sourceArtifactId: assemblyInput.sourceArtificerArtifactId ?? undefined,
1236
1528
  });
1237
1529
  if (!traceBuild.ok) {
1238
1530
  this.emitEvent('rule_assembly_failed', taskId, {
@@ -1255,7 +1547,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1255
1547
  // adversarialResult.passed === true is the precondition for this method;
1256
1548
  // the gate decision is therefore accepted_shadow.
1257
1549
  ruleHostGateDecision: 'accepted_shadow',
1258
- sourceArtificerArtifactId: context.sourceArtificerArtifactId ?? output.sourceArtificerArtifactId,
1550
+ sourceArtificerArtifactId: assemblyInput.sourceArtificerArtifactId ?? output.sourceArtificerArtifactId,
1259
1551
  adversarialResult: output.adversarialResult,
1260
1552
  ...(requiresContextVersion === 2 ? { requiresContextVersion } : {}),
1261
1553
  // PRI-490: preserve evidenceRefs from Artificer artifact into rule artifact.