@principles/core 1.270.5 → 1.272.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 (188) hide show
  1. package/dist/runtime-v2/__tests__/admission-gate.test.js +23 -6
  2. package/dist/runtime-v2/__tests__/admission-gate.test.js.map +1 -1
  3. package/dist/runtime-v2/__tests__/adversarial-loop.test.js +22 -0
  4. package/dist/runtime-v2/__tests__/adversarial-loop.test.js.map +1 -1
  5. package/dist/runtime-v2/__tests__/context-payload-validation.test.js +1 -1
  6. package/dist/runtime-v2/__tests__/context-payload-validation.test.js.map +1 -1
  7. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js +20 -0
  8. package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
  9. package/dist/runtime-v2/__tests__/evidence-guards.test.js +2 -2
  10. package/dist/runtime-v2/__tests__/evidence-guards.test.js.map +1 -1
  11. package/dist/runtime-v2/__tests__/golden-path-diagnostician-e2e.test.js +1 -1
  12. package/dist/runtime-v2/__tests__/golden-path-diagnostician-e2e.test.js.map +1 -1
  13. package/dist/runtime-v2/__tests__/governance-experience.test.js +19 -0
  14. package/dist/runtime-v2/__tests__/governance-experience.test.js.map +1 -1
  15. package/dist/runtime-v2/__tests__/pain-evidence-contract.test.js +3 -3
  16. package/dist/runtime-v2/__tests__/pain-evidence-contract.test.js.map +1 -1
  17. package/dist/runtime-v2/__tests__/pain-signal-bridge-result-shaping.test.js +1 -1
  18. package/dist/runtime-v2/__tests__/pain-signal-bridge-result-shaping.test.js.map +1 -1
  19. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +26 -5
  20. package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -1
  21. package/dist/runtime-v2/__tests__/rollout-rule-candidate-contract.test.d.ts +2 -0
  22. package/dist/runtime-v2/__tests__/rollout-rule-candidate-contract.test.d.ts.map +1 -0
  23. package/dist/runtime-v2/__tests__/rollout-rule-candidate-contract.test.js +243 -0
  24. package/dist/runtime-v2/__tests__/rollout-rule-candidate-contract.test.js.map +1 -0
  25. package/dist/runtime-v2/admission-gate.d.ts +8 -1
  26. package/dist/runtime-v2/admission-gate.d.ts.map +1 -1
  27. package/dist/runtime-v2/admission-gate.js +14 -0
  28. package/dist/runtime-v2/admission-gate.js.map +1 -1
  29. package/dist/runtime-v2/context-payload.d.ts +9 -3
  30. package/dist/runtime-v2/context-payload.d.ts.map +1 -1
  31. package/dist/runtime-v2/context-payload.js +3 -1
  32. package/dist/runtime-v2/context-payload.js.map +1 -1
  33. package/dist/runtime-v2/evidence-triage/__tests__/observation-resolver.test.js +2 -2
  34. package/dist/runtime-v2/evidence-triage/__tests__/observation-resolver.test.js.map +1 -1
  35. package/dist/runtime-v2/evidence-triage/observation-resolver.d.ts +1 -1
  36. package/dist/runtime-v2/evidence-triage/observation-resolver.d.ts.map +1 -1
  37. package/dist/runtime-v2/evidence-triage/observation-resolver.js +2 -2
  38. package/dist/runtime-v2/evidence-triage/observation-resolver.js.map +1 -1
  39. package/dist/runtime-v2/evidence-triage/types.d.ts +1 -1
  40. package/dist/runtime-v2/evidence-triage/types.d.ts.map +1 -1
  41. package/dist/runtime-v2/evolution/evolution-types.d.ts +8 -4
  42. package/dist/runtime-v2/evolution/evolution-types.d.ts.map +1 -1
  43. package/dist/runtime-v2/evolution/evolution-types.js +3 -1
  44. package/dist/runtime-v2/evolution/evolution-types.js.map +1 -1
  45. package/dist/runtime-v2/governance-experience-contract.d.ts +16 -16
  46. package/dist/runtime-v2/governance-experience-contract.d.ts.map +1 -1
  47. package/dist/runtime-v2/governance-experience-contract.js +4 -1
  48. package/dist/runtime-v2/governance-experience-contract.js.map +1 -1
  49. package/dist/runtime-v2/governance-experience.d.ts.map +1 -1
  50. package/dist/runtime-v2/governance-experience.js +30 -8
  51. package/dist/runtime-v2/governance-experience.js.map +1 -1
  52. package/dist/runtime-v2/index.d.ts +11 -1
  53. package/dist/runtime-v2/index.d.ts.map +1 -1
  54. package/dist/runtime-v2/index.js +7 -0
  55. package/dist/runtime-v2/index.js.map +1 -1
  56. package/dist/runtime-v2/internalization/__tests__/artificer-code-bearing.test.d.ts +2 -0
  57. package/dist/runtime-v2/internalization/__tests__/artificer-code-bearing.test.d.ts.map +1 -0
  58. package/dist/runtime-v2/internalization/__tests__/artificer-code-bearing.test.js +114 -0
  59. package/dist/runtime-v2/internalization/__tests__/artificer-code-bearing.test.js.map +1 -0
  60. package/dist/runtime-v2/internalization/__tests__/evaluator-gate-authority.test.d.ts +2 -0
  61. package/dist/runtime-v2/internalization/__tests__/evaluator-gate-authority.test.d.ts.map +1 -0
  62. package/dist/runtime-v2/internalization/__tests__/evaluator-gate-authority.test.js +256 -0
  63. package/dist/runtime-v2/internalization/__tests__/evaluator-gate-authority.test.js.map +1 -0
  64. package/dist/runtime-v2/internalization/__tests__/evaluator-prompt-builder.test.js +1 -1
  65. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +15 -14
  66. package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -1
  67. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts +2 -0
  68. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.d.ts.map +1 -0
  69. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js +56 -0
  70. package/dist/runtime-v2/internalization/__tests__/owner-decision-architecture.test.js.map +1 -0
  71. package/dist/runtime-v2/internalization/__tests__/owner-decision-review.test.d.ts +2 -0
  72. package/dist/runtime-v2/internalization/__tests__/owner-decision-review.test.d.ts.map +1 -0
  73. package/dist/runtime-v2/internalization/__tests__/owner-decision-review.test.js +229 -0
  74. package/dist/runtime-v2/internalization/__tests__/owner-decision-review.test.js.map +1 -0
  75. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts +2 -0
  76. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.d.ts.map +1 -0
  77. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js +1054 -0
  78. package/dist/runtime-v2/internalization/__tests__/owner-decision.test.js.map +1 -0
  79. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts +2 -0
  80. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.d.ts.map +1 -0
  81. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js +328 -0
  82. package/dist/runtime-v2/internalization/__tests__/owner-override-resume.test.js.map +1 -0
  83. package/dist/runtime-v2/internalization/__tests__/rule-activation-contract.test.d.ts +2 -0
  84. package/dist/runtime-v2/internalization/__tests__/rule-activation-contract.test.d.ts.map +1 -0
  85. package/dist/runtime-v2/internalization/__tests__/rule-activation-contract.test.js +187 -0
  86. package/dist/runtime-v2/internalization/__tests__/rule-activation-contract.test.js.map +1 -0
  87. package/dist/runtime-v2/internalization/artificer-code-bearing.d.ts +39 -0
  88. package/dist/runtime-v2/internalization/artificer-code-bearing.d.ts.map +1 -0
  89. package/dist/runtime-v2/internalization/artificer-code-bearing.js +33 -0
  90. package/dist/runtime-v2/internalization/artificer-code-bearing.js.map +1 -0
  91. package/dist/runtime-v2/internalization/evaluator-output.d.ts +63 -2
  92. package/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  93. package/dist/runtime-v2/internalization/evaluator-output.js +139 -2
  94. package/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  95. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +54 -2
  96. package/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  97. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js +49 -2
  98. package/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  99. package/dist/runtime-v2/internalization/evaluator-runner.d.ts +45 -8
  100. package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  101. package/dist/runtime-v2/internalization/evaluator-runner.js +465 -43
  102. package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  103. package/dist/runtime-v2/internalization/index.d.ts +2 -0
  104. package/dist/runtime-v2/internalization/index.d.ts.map +1 -1
  105. package/dist/runtime-v2/internalization/index.js +1 -0
  106. package/dist/runtime-v2/internalization/index.js.map +1 -1
  107. package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  108. package/dist/runtime-v2/internalization/internalization-orchestrator.js +10 -54
  109. package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  110. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +7 -1
  111. package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -1
  112. package/dist/runtime-v2/internalization/internalization-transition-decision.js +9 -2
  113. package/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -1
  114. package/dist/runtime-v2/internalization/owner-decision-review.d.ts +98 -0
  115. package/dist/runtime-v2/internalization/owner-decision-review.d.ts.map +1 -0
  116. package/dist/runtime-v2/internalization/owner-decision-review.js +287 -0
  117. package/dist/runtime-v2/internalization/owner-decision-review.js.map +1 -0
  118. package/dist/runtime-v2/internalization/owner-resolution-service.d.ts +100 -0
  119. package/dist/runtime-v2/internalization/owner-resolution-service.d.ts.map +1 -0
  120. package/dist/runtime-v2/internalization/owner-resolution-service.js +385 -0
  121. package/dist/runtime-v2/internalization/owner-resolution-service.js.map +1 -0
  122. package/dist/runtime-v2/internalization/owner-retry.d.ts +12 -0
  123. package/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -1
  124. package/dist/runtime-v2/internalization/owner-retry.js +23 -0
  125. package/dist/runtime-v2/internalization/owner-retry.js.map +1 -1
  126. package/dist/runtime-v2/internalization/owner-review.d.ts +199 -0
  127. package/dist/runtime-v2/internalization/owner-review.d.ts.map +1 -0
  128. package/dist/runtime-v2/internalization/owner-review.js +470 -0
  129. package/dist/runtime-v2/internalization/owner-review.js.map +1 -0
  130. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +5 -1
  131. package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  132. package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  133. package/dist/runtime-v2/internalization/pitask-metadata.d.ts +84 -0
  134. package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  135. package/dist/runtime-v2/internalization/pitask-metadata.js +166 -0
  136. package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  137. package/dist/runtime-v2/internalization/revision-reopen.d.ts +56 -0
  138. package/dist/runtime-v2/internalization/revision-reopen.d.ts.map +1 -0
  139. package/dist/runtime-v2/internalization/revision-reopen.js +144 -0
  140. package/dist/runtime-v2/internalization/revision-reopen.js.map +1 -0
  141. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +25 -0
  142. package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
  143. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js +202 -23
  144. package/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
  145. package/dist/runtime-v2/internalization/rule-activation-contract.d.ts +35 -0
  146. package/dist/runtime-v2/internalization/rule-activation-contract.d.ts.map +1 -0
  147. package/dist/runtime-v2/internalization/rule-activation-contract.js +36 -0
  148. package/dist/runtime-v2/internalization/rule-activation-contract.js.map +1 -0
  149. package/dist/runtime-v2/pain-signal-bridge.d.ts +2 -0
  150. package/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  151. package/dist/runtime-v2/pain-signal-bridge.js +4 -3
  152. package/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  153. package/dist/runtime-v2/pain-to-principle-service.d.ts +1 -0
  154. package/dist/runtime-v2/pain-to-principle-service.d.ts.map +1 -1
  155. package/dist/runtime-v2/pain-to-principle-service.js +1 -0
  156. package/dist/runtime-v2/pain-to-principle-service.js.map +1 -1
  157. package/dist/runtime-v2/runner/__tests__/pain-signal-bridge-admission.test.js +6 -6
  158. package/dist/runtime-v2/runner/__tests__/pain-signal-bridge-admission.test.js.map +1 -1
  159. package/dist/runtime-v2/store/context/sqlite-context-assembler.d.ts.map +1 -1
  160. package/dist/runtime-v2/store/context/sqlite-context-assembler.js +25 -5
  161. package/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
  162. package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js +39 -9
  163. package/dist/runtime-v2/store/context/sqlite-context-assembler.test.js.map +1 -1
  164. package/dist/runtime-v2/store/runtime-state-manager.d.ts +12 -0
  165. package/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  166. package/dist/runtime-v2/store/runtime-state-manager.js +17 -0
  167. package/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  168. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts +2 -0
  169. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.d.ts.map +1 -0
  170. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js +114 -0
  171. package/dist/runtime-v2/store/task/__tests__/sqlite-task-store-cas.test.js.map +1 -0
  172. package/dist/runtime-v2/store/task/memory-task-store.d.ts +1 -0
  173. package/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
  174. package/dist/runtime-v2/store/task/memory-task-store.js +8 -0
  175. package/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
  176. package/dist/runtime-v2/store/task/sqlite-task-store.d.ts +20 -0
  177. package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
  178. package/dist/runtime-v2/store/task/sqlite-task-store.js +81 -0
  179. package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
  180. package/dist/runtime-v2/store/task/task-store.d.ts +9 -0
  181. package/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
  182. package/dist/runtime-v2/store/workspace-isolation.test.js +1 -0
  183. package/dist/runtime-v2/store/workspace-isolation.test.js.map +1 -1
  184. package/dist/runtime-v2/task-status.d.ts +2 -1
  185. package/dist/runtime-v2/task-status.d.ts.map +1 -1
  186. package/dist/runtime-v2/task-status.js +5 -1
  187. package/dist/runtime-v2/task-status.js.map +1 -1
  188. package/package.json +1 -1
@@ -1,7 +1,11 @@
1
1
  import { isEvaluatorOutputV2 } from './evaluator-output.js';
2
+ // PRI-634 A2 (authority migration): gate necessity derives from the durable
3
+ // Artificer artifact, not from optional LLM output shape.
4
+ import { assessArtificerCodeBearing } from './artificer-code-bearing.js';
2
5
  import { PDRuntimeError, isPDErrorCategory } from '../error-categories.js';
3
6
  import { hydratePITaskRecord, createPITaskDiagnosticJson, mergePITaskMetadata } from './pitask-metadata.js';
4
- import { EvaluatorPromptBuilder } from './evaluator-prompt-builder.js';
7
+ import { HUMAN_REVIEW_REASON, planOwnerVerdictOverrideResume, markOwnerResolutionApplied, computeArtifactContentHash, } from './owner-review.js';
8
+ import { EvaluatorPromptBuilder, deriveRequirementLedger } from './evaluator-prompt-builder.js';
5
9
  import { reconcileLineageEcho } from './peer-runner-contracts.js';
6
10
  import { BasePeerRunner } from '../runner/base-peer-runner.js';
7
11
  import { EVALUATOR_STAGE1_MANIFEST, EVALUATOR_STAGE2_MANIFEST } from './context-manifests.js';
@@ -17,6 +21,31 @@ import { canonicalizeToolKind } from './rule-context-v2.js';
17
21
  function isRecordValue(value) {
18
22
  return value !== null && typeof value === 'object' && !Array.isArray(value);
19
23
  }
24
+ /**
25
+ * PRI-634: resolve a golden-trace case's target path across the host tool
26
+ * schemas actually observed on real chains.
27
+ *
28
+ * Golden-trace params are echoed verbatim from the host trajectory
29
+ * (behavior-example-pack-assembler `params: parsedParams`), so the field name
30
+ * follows whatever the host tool used — the dominant write-tool schema spells
31
+ * it `file_path` (OpenClaw / Claude Code), while PRI-485's v2 generator only
32
+ * read `path`. Reading a single spelling made v2 auto-generation structurally
33
+ * unreachable: the merged adversarial set stayed empty, the deterministic gate
34
+ * never ran, and an LLM-declared `adversarialResult.passed = true` silently
35
+ * stood in for a real replay (chain 48371236).
36
+ *
37
+ * Returns null when neither spelling yields a non-empty string.
38
+ */
39
+ function resolveCasePathParam(params) {
40
+ if (!isRecordValue(params))
41
+ return null;
42
+ for (const key of ['path', 'file_path']) {
43
+ const raw = params[key];
44
+ if (typeof raw === 'string' && raw.trim() !== '')
45
+ return raw;
46
+ }
47
+ return null;
48
+ }
20
49
  /**
21
50
  * Extract scribeArtifactId from an artificer artifact's contentJson (PRD Decision 12).
22
51
  * The contentJson is untrusted — parsed defensively with type guards, never as-cast
@@ -132,6 +161,8 @@ export class EvaluatorRunner extends BasePeerRunner {
132
161
  * Null when the deps did not inject the seeder (= repair seeding unavailable).
133
162
  */
134
163
  repairTaskSeeder;
164
+ /** PRI-630: runtime-authoritative tool facts; null = catalog unavailable (degraded rule in prompt) */
165
+ hostToolCatalog;
135
166
  constructor(deps, options) {
136
167
  super(deps, options, {
137
168
  runnerName: 'evaluator',
@@ -143,6 +174,7 @@ export class EvaluatorRunner extends BasePeerRunner {
143
174
  this.gateDeps = options.gateDeps ?? null;
144
175
  this.repairLoopEnabledResolver = deps.isRepairLoopEnabled ?? null;
145
176
  this.repairTaskSeeder = deps.seedArtificerRepairTask ?? null;
177
+ this.hostToolCatalog = options.hostToolCatalog ?? null;
146
178
  }
147
179
  /**
148
180
  * Returns true iff the evaluator→artificer repair loop feature flag is on.
@@ -179,6 +211,8 @@ export class EvaluatorRunner extends BasePeerRunner {
179
211
  });
180
212
  continue;
181
213
  }
214
+ const depPi = hydratePITaskRecord(depTask);
215
+ const dependencyRepairPayload = depPi?.repairPayload;
182
216
  const artifacts = await this.artifactStore.listBySourceTaskId(depId);
183
217
  if (artifacts.length > 0) {
184
218
  const [firstArtifact] = artifacts;
@@ -211,12 +245,19 @@ export class EvaluatorRunner extends BasePeerRunner {
211
245
  const contextRefs = scribeContent && scribeRef
212
246
  ? [artifactRef, scribeRef]
213
247
  : [artifactRef];
248
+ // PRI-630: 修复轮 (repairPayload 存在) 时解析上轮评估上下文
249
+ let previousEvaluation;
250
+ if (dependencyRepairPayload) {
251
+ previousEvaluation = await this.resolvePreviousEvaluation(taskId, dependencyRepairPayload, firstArtifact.contentJson);
252
+ }
214
253
  return {
215
254
  contextHash: BasePeerRunner.hashContextRefs(contextRefs),
216
255
  artificerArtifact: firstArtifact.contentJson,
217
256
  sourceArtificerArtifactId: firstArtifact.artifactId,
218
257
  scribeArtifact: scribeContent,
219
258
  sourceScribeArtifactId: scribeRef,
259
+ ...(dependencyRepairPayload !== undefined ? { dependencyRepairPayload } : {}),
260
+ ...(previousEvaluation !== undefined ? { previousEvaluation } : {}),
220
261
  };
221
262
  }
222
263
  }
@@ -312,6 +353,116 @@ export class EvaluatorRunner extends BasePeerRunner {
312
353
  * Build the evaluator prompt with the given manifest's resolved context.
313
354
  * Shared by single-stage and two-stage paths.
314
355
  */
356
+ /**
357
+ * PRI-630 收敛契约 (SPEC §18.1): 解析上轮评估上下文 — 从 dependency
358
+ * artificer 的 repairPayload.sourceEvaluatorTaskId 找到上轮 evaluator,
359
+ * 读取其最近 principle artifact,按 rc-1/rc-2 守卫解析 evaluation 字段。
360
+ * requirements 用稳定 id (req-1..N, 上轮 requiredChanges 顺序)。
361
+ * 解析失败 → 结构化降级事件 + undefined (保持既有行为,可观测)。
362
+ */
363
+ async resolvePreviousEvaluation(taskId, repairPayload, repairArtifactContentJson) {
364
+ const priorTaskId = repairPayload.sourceEvaluatorTaskId;
365
+ const priorArtifactJson = await this.artifactStore
366
+ .listBySourceTaskId(priorTaskId)
367
+ .then((artifacts) => {
368
+ let latest = null;
369
+ for (const a of artifacts) {
370
+ if (a.artifactKind !== 'principle')
371
+ continue;
372
+ if (!latest || a.updatedAt > latest.updatedAt)
373
+ latest = a;
374
+ }
375
+ return latest?.contentJson ?? null;
376
+ })
377
+ .catch(() => null);
378
+ if (priorArtifactJson === null) {
379
+ this.emitEvent('previous_evaluation_context_degraded', taskId, {
380
+ priorEvaluatorTaskId: priorTaskId,
381
+ reason: 'prior_evaluation_artifact_unavailable',
382
+ repairIteration: repairPayload.repairIteration,
383
+ });
384
+ return undefined;
385
+ }
386
+ let parsed;
387
+ try {
388
+ parsed = JSON.parse(priorArtifactJson);
389
+ }
390
+ catch {
391
+ this.emitEvent('previous_evaluation_context_degraded', taskId, {
392
+ priorEvaluatorTaskId: priorTaskId,
393
+ reason: 'prior_evaluation_artifact_unparseable',
394
+ });
395
+ return undefined;
396
+ }
397
+ if (typeof parsed !== 'object' || parsed === null)
398
+ return undefined;
399
+ // runtime-contract-exempt: ERR-001 object-guarded unknown property extraction; typeof guard follows immediately
400
+ const { evaluation } = parsed;
401
+ if (typeof evaluation !== 'object' || evaluation === null) {
402
+ this.emitEvent('previous_evaluation_context_degraded', taskId, {
403
+ priorEvaluatorTaskId: priorTaskId,
404
+ reason: 'prior_evaluation_shape_invalid',
405
+ });
406
+ return undefined;
407
+ }
408
+ const ev = evaluation;
409
+ const decision = typeof ev.decision === 'string' ? ev.decision : 'needs_revision';
410
+ const score = typeof ev.score === 'number' && Number.isFinite(ev.score) ? Math.min(1, Math.max(0, ev.score)) : 0;
411
+ const toStringArray = (v, cap) => {
412
+ if (!Array.isArray(v))
413
+ return [];
414
+ const out = [];
415
+ for (const item of v.slice(0, cap)) {
416
+ if (typeof item === 'string' && item.trim() !== '')
417
+ out.push(item.slice(0, 500));
418
+ }
419
+ return out;
420
+ };
421
+ const concerns = toStringArray(ev.concerns, 10);
422
+ const requiredChanges = toStringArray(ev.requiredChanges, 10);
423
+ // PRI-630 P1 评审修复: 需求身份跨轮稳定 — 上轮 echo 的 requirementLedger
424
+ // (若有) 中 still_open/regressed 条目保留原 id 与原 statement;本轮
425
+ // requiredChanges 的非重述项作为新需求从最大序号递增。无 ledger 时
426
+ // (首个修复轮) 退回顺序编号。
427
+ let prevLedger;
428
+ if (Array.isArray(ev.requirementLedger)) {
429
+ prevLedger = [];
430
+ for (const entry of ev.requirementLedger) {
431
+ if (entry === null || typeof entry !== 'object')
432
+ continue;
433
+ const rec = entry;
434
+ if (typeof rec.id !== 'string' || typeof rec.statement !== 'string')
435
+ continue;
436
+ if (rec.status !== 'resolved' && rec.status !== 'still_open' && rec.status !== 'regressed')
437
+ continue;
438
+ prevLedger.push({ id: rec.id, statement: rec.statement, status: rec.status });
439
+ }
440
+ }
441
+ const requirements = deriveRequirementLedger(prevLedger, requiredChanges);
442
+ // 修复说明: 当前 (被修复) artificer artifact 的声明性摘要 — 有界提取
443
+ let repairSummary;
444
+ try {
445
+ const art = JSON.parse(repairArtifactContentJson);
446
+ if (typeof art === 'object' && art !== null) {
447
+ // runtime-contract-exempt: ERR-001 object-guarded unknown property extraction; typeof guard follows immediately
448
+ const { implementationSummary } = art;
449
+ if (typeof implementationSummary === 'string' && implementationSummary.trim() !== '') {
450
+ repairSummary = implementationSummary.slice(0, 800);
451
+ }
452
+ }
453
+ }
454
+ catch {
455
+ repairSummary = undefined;
456
+ }
457
+ return {
458
+ decision,
459
+ score,
460
+ concerns,
461
+ requirements,
462
+ repairIteration: repairPayload.repairIteration,
463
+ ...(repairSummary !== undefined ? { repairSummary } : {}),
464
+ };
465
+ }
315
466
  buildEvaluatorPrompt(taskId, context, manifest) {
316
467
  let parsedArtificerArtifact = null;
317
468
  if (context.artificerArtifact) {
@@ -346,6 +497,8 @@ export class EvaluatorRunner extends BasePeerRunner {
346
497
  artificerArtifact: parsedArtificerArtifact,
347
498
  scribeArtifact: parsedScribeArtifact,
348
499
  sourceArtificerArtifactId: context.sourceArtificerArtifactId ?? '',
500
+ previousEvaluation: context.previousEvaluation,
501
+ hostToolCatalog: this.hostToolCatalog ?? undefined,
349
502
  });
350
503
  return message;
351
504
  }
@@ -362,7 +515,12 @@ export class EvaluatorRunner extends BasePeerRunner {
362
515
  });
363
516
  }
364
517
  async validateOutput(output, taskId, context) {
365
- const result = await this.validator.validate(output, taskId, context.sourceArtificerArtifactId ?? undefined);
518
+ // PRI-630 P1 评审修复: 修复轮把上轮 requirement ids 传入做完整覆盖校验
519
+ // (缺 priorRequirementStatuses 或漏 id → output_invalid),不再仅靠 prompt。
520
+ const convergence = context.previousEvaluation
521
+ ? { expectedRequirements: context.previousEvaluation.requirements.map((r) => ({ id: r.id, statement: r.statement })) }
522
+ : undefined;
523
+ const result = await this.validator.validate(output, taskId, context.sourceArtificerArtifactId ?? undefined, convergence);
366
524
  // Trust-boundary: validator is an injected dependency returning `string | undefined`
367
525
  // for errorCategory. We must not `as`-cast; validate at runtime (ERR-001, ERR-005).
368
526
  const rawCategory = result.errorCategory;
@@ -457,21 +615,64 @@ export class EvaluatorRunner extends BasePeerRunner {
457
615
  });
458
616
  }
459
617
  // ── PRI-426: single-round adversarial sandbox replay ──
460
- // Runs only when (a) the output is V2 (code-bearing), (b) gateDeps is
461
- // injected, and (c) passive review passed (LLM short-circuits to
462
- // needs_revision when any of the 3 dimensions fail, so by the time we get
463
- // here with decision='approved' the passive review already passed).
618
+ // PRI-634 A2 (authority migration): gate necessity is decided by the
619
+ // DURABLE Artificer artifact assessArtificerCodeBearing() mirrors
620
+ // assembleRuleArtifact()'s static preconditions exactly never by
621
+ // whether the LLM happened to emit optional V2 fields
622
+ // (isEvaluatorOutputV2). A V2-shaped output over a non-code-bearing
623
+ // Artificer keeps the legacy behavior (attempt replay, degrade with
624
+ // telemetry) so the old path stays observable.
464
625
  //
465
626
  // PRI-423 contract: adversarialCasesToGoldenTrace yields an all-negative
466
627
  // trace. We MUST merge ≥1 positive case from the Artificer golden trace
467
628
  // before replaying, otherwise the merged trace fails validateGoldenTrace.
468
629
  //
469
- // This block never throws into the caller — a sandbox/gate failure degrades
470
- // to adversarialResult.passed=false with a structured reason (ERR-018).
471
- // The principle artifact is already persisted, so prompt-channel fallback
472
- // remains available regardless of replay outcome (PRD Decision 11d §h).
630
+ // The replay itself never throws into the caller — a sandbox/gate failure
631
+ // degrades to adversarialResult.passed=false with a structured reason
632
+ // (ERR-018). The principle artifact is already persisted, so
633
+ // prompt-channel fallback remains available regardless of replay outcome
634
+ // (PRD Decision 11d §h). The ONE deliberate exception is the R2 wiring
635
+ // guard below: a code-bearing artifact without gateDeps fails loud
636
+ // (capability_missing, permanent) instead of succeeding un-gated.
473
637
  let finalOutput = output;
474
- if (isEvaluatorOutputV2(output) && this.gateDeps) {
638
+ const gateAssessment = assessArtificerCodeBearing(context.artificerArtifact);
639
+ const outputWantsGate = isEvaluatorOutputV2(output);
640
+ const evaluatorDecision = output.evaluation.decision;
641
+ if (evaluatorDecision !== 'approved') {
642
+ // Passive review short-circuit: the LLM emits needs_revision/rejected
643
+ // when its review fails — no gate needed. Previously silent inside
644
+ // runAdversarialReplay; now observable at the gate decision point
645
+ // (Runtime Contract Rule 9).
646
+ if (gateAssessment.codeBearing || outputWantsGate) {
647
+ this.emitEvent('adversarial_replay_skipped', taskId, {
648
+ runId,
649
+ reason: 'evaluation_not_approved',
650
+ nextAction: 'repair_loop_or_next_review_round',
651
+ });
652
+ }
653
+ }
654
+ else if (!this.gateDeps) {
655
+ this.emitEvent('adversarial_replay_skipped', taskId, {
656
+ runId,
657
+ reason: 'gate_deps_not_injected',
658
+ nextAction: 'wire_gateDeps_createProductionGateDeps_into_evaluator_runner_assembly',
659
+ });
660
+ if (gateAssessment.codeBearing) {
661
+ // PRI-634 R2 (wiring regression guard): a code-bearing Artificer
662
+ // artifact REQUIRES the deterministic gate. Proceeding would yield
663
+ // succeeded + adversarialResult=null — the exact state that broke
664
+ // chain 48371236. capability_missing is a permanent error: the task
665
+ // fails loud (markTaskFailed) instead of retry-burning LLM budget —
666
+ // a missing gateDeps is an assembly defect, not a transient fault.
667
+ return this.retryOrFail({
668
+ taskId,
669
+ task,
670
+ errorCategory: 'capability_missing',
671
+ failureReason: `PRI-634 R2: Artificer artifact ${context.sourceArtificerArtifactId ?? '(unknown)'} is code-bearing but EvaluatorRunner was assembled without gateDeps — deterministic adversarial replay cannot run. Fix: inject gateDeps: createProductionGateDeps() into the EvaluatorRunner assembly.`,
672
+ });
673
+ }
674
+ }
675
+ else if (gateAssessment.codeBearing || outputWantsGate) {
475
676
  const replayOutcome = await this.runAdversarialReplay(output, taskId, runId, context);
476
677
  if (replayOutcome.updatedOutput) {
477
678
  finalOutput = replayOutcome.updatedOutput;
@@ -503,6 +704,30 @@ export class EvaluatorRunner extends BasePeerRunner {
503
704
  });
504
705
  }
505
706
  }
707
+ else if (gateAssessment.codeBearing) {
708
+ // ── PRI-634 R3 (terminal-state invariant) ──
709
+ // A code-bearing Artificer artifact that reached `approved` MUST leave
710
+ // this block carrying an adversarialResult. Anything else reproduces
711
+ // the chain-48371236 terminal state — approved with the deterministic
712
+ // gate never executed, adversarialResult=null, and therefore no
713
+ // pi-rule-* downstream — regardless of WHICH cause prevented the
714
+ // replay (zero adversarial cases, no positive case, unparseable
715
+ // artifact, conversion drift…). Telemetry is not recovery: a task that
716
+ // ends `succeeded` here is indistinguishable from the original defect.
717
+ //
718
+ // errorCategory is `input_invalid` (already listed in
719
+ // permanentErrorCategories) rather than `capability_missing`: the gate
720
+ // IS wired correctly (R2 owns the wiring case) — the upstream
721
+ // Artificer content simply cannot be turned into gate input. Permanent
722
+ // because retrying the LLM cannot make the Artificer emit the missing
723
+ // affectedTools / positive-case path.
724
+ return this.retryOrFail({
725
+ taskId,
726
+ task,
727
+ errorCategory: 'input_invalid',
728
+ failureReason: `PRI-634 R3: Artificer artifact ${context.sourceArtificerArtifactId ?? '(unknown)'} is code-bearing and the evaluator approved it, but the deterministic adversarial replay did not run (reason: ${replayOutcome.skipReason ?? 'unknown'}). Refusing to report succeeded with adversarialResult=null — that is the chain-48371236 terminal state. Fix: have the Artificer emit affectedTools plus at least one positive golden-trace case carrying a path, or supply LLM adversarialCases.`,
729
+ });
730
+ }
506
731
  }
507
732
  // ── P0 (verdict drift): verdict + completion intent 原子落库 ──
508
733
  // 必须先于一切治理 side effect (validate bearer / seed repair / rule
@@ -568,7 +793,7 @@ export class EvaluatorRunner extends BasePeerRunner {
568
793
  */
569
794
  async applyEvaluatorDecisionEffects(args) {
570
795
  const { taskId, runId, finalOutput, task, artifactId, contextHash, sourceArtificerArtifactId } = args;
571
- const { decision } = finalOutput.evaluation;
796
+ const decision = args.decisionOverride ?? finalOutput.evaluation.decision;
572
797
  // ── Evaluator-specific: validate principle-bearing Scribe artifact ──
573
798
  // This is the critical business logic: approved evaluator must validate
574
799
  // the Scribe principle artifact, NOT the Artificer plan artifact.
@@ -632,6 +857,10 @@ export class EvaluatorRunner extends BasePeerRunner {
632
857
  if (decision === 'needs_revision' && this.isRepairLoopEnabled()) {
633
858
  const repairOutcome = await this.maybeSeedArtificerRepair(taskId, { runId, output: finalOutput, sourceArtificerArtifactId });
634
859
  if (repairOutcome.kind === 'max_iterations_reached') {
860
+ // PRI-629: budget 耗尽(decision-capable)与 seed 失败(recovery)拆分原因码
861
+ const reasonCode = repairOutcome.detail === 'budget_exhausted'
862
+ ? HUMAN_REVIEW_REASON.evaluatorRepairBudgetExhausted
863
+ : HUMAN_REVIEW_REASON.evaluatorRepairSeedFailed;
635
864
  // Fail loud (rc-9, EP-03, ERR-002): mark the task needs_human_review
636
865
  // so it does NOT stay in 'leased' state (which would cause the lease
637
866
  // to expire and the evaluator to re-run the same verdict infinitely).
@@ -639,14 +868,14 @@ export class EvaluatorRunner extends BasePeerRunner {
639
868
  // fail-closed: 写失败 throw → retry_wait → 入口门 resume 同一效果,
640
869
  // 不问 LLM;禁止吞错后继续 (intent applied ⇔ effect 已 durable)。
641
870
  const resultRef = `${this.config.resultRefPrefix}://${runId}`;
642
- await this.markNeedsHumanReviewOrThrow(taskId, runId, 'repair_loop_max_iterations_or_seed_failure');
871
+ await this.markNeedsHumanReviewOrThrow(taskId, { runId, reasonCode, sourceArtifactId: artifactId });
643
872
  this.emitEvent('task_needs_human_review', taskId, {
644
873
  attemptCount: task.attemptCount,
645
874
  resultRef,
646
875
  evaluationDecision: finalOutput.evaluation.decision,
647
876
  evaluationScore: finalOutput.evaluation.score,
648
877
  ruleArtifactId: null,
649
- reason: 'repair_loop_max_iterations_or_seed_failure',
878
+ reason: `repair_loop_${reasonCode}`,
650
879
  });
651
880
  return {
652
881
  kind: 'human_review',
@@ -711,9 +940,39 @@ export class EvaluatorRunner extends BasePeerRunner {
711
940
  * effect,不问 LLM;禁止吞错后让 caller 标 intent applied
712
941
  * (intent applied ⇔ 其 durable effect 已 materialize)。
713
942
  */
714
- async markNeedsHumanReviewOrThrow(taskId, runId, reason) {
943
+ async markNeedsHumanReviewOrThrow(taskId, review) {
944
+ const { runId, reasonCode, sourceArtifactId } = review;
715
945
  try {
716
- await this.stateManager.updateTask(taskId, { status: 'needs_human_review' });
946
+ // PRI-629: status + humanReviewContext 同一次 task-row mutation 原子落库
947
+ // (SPEC §4 — context 缺失的 NHR 是 legacy,只能靠推断)。
948
+ const raw = await this.stateManager.getTask(taskId);
949
+ if (!raw)
950
+ throw new Error(`task ${taskId} not found`);
951
+ const piTask = hydratePITaskRecord(raw);
952
+ if (!piTask)
953
+ throw new Error(`task ${taskId} not hydratable`);
954
+ let sourceArtifactHash;
955
+ try {
956
+ const artifact = await this.artifactStore.getArtifactById(sourceArtifactId);
957
+ if (artifact)
958
+ sourceArtifactHash = computeArtifactContentHash(artifact.contentJson);
959
+ }
960
+ catch {
961
+ sourceArtifactHash = undefined; // hash 可选 — capability 侧要求 artifact 存在,届时重算
962
+ }
963
+ const context = {
964
+ reasonCode,
965
+ sourceRunId: runId,
966
+ sourceArtifactId,
967
+ ...(sourceArtifactHash !== undefined ? { sourceArtifactHash } : {}),
968
+ revisionEpoch: piTask.revisionCount ?? 0,
969
+ createdAt: new Date().toISOString(),
970
+ };
971
+ const merged = mergePITaskMetadata(piTask, { humanReviewContext: context });
972
+ await this.stateManager.updateTask(taskId, {
973
+ status: 'needs_human_review',
974
+ diagnosticJson: createPITaskDiagnosticJson(merged),
975
+ });
717
976
  // read-back invariant (INV-2): 只有 effect durable 才允许 caller 标 applied
718
977
  const current = await this.stateManager.getTask(taskId);
719
978
  if (!current || current.status !== 'needs_human_review') {
@@ -723,7 +982,7 @@ export class EvaluatorRunner extends BasePeerRunner {
723
982
  catch (err) {
724
983
  this.emitEvent('repair_loop_mark_review_failed', taskId, {
725
984
  runId,
726
- reason,
985
+ reason: reasonCode,
727
986
  errorMessage: err instanceof Error ? err.message : String(err),
728
987
  nextAction: 'task_will_retry_then_resume_completion_intent_without_llm',
729
988
  });
@@ -797,6 +1056,14 @@ export class EvaluatorRunner extends BasePeerRunner {
797
1056
  */
798
1057
  async maybeResumePendingIntent(taskId, leasedTask) {
799
1058
  const piTask = hydratePITaskRecord(leasedTask);
1059
+ // ── PRI-629: pending Owner Resolution 优先于一切 (SPEC §10) ──
1060
+ // Owner accept_current / reject_current 已 durable 记录且任务被翻回
1061
+ // pending — 本次 run 应用 override,绝不重新调用 LLM。applied 但未
1062
+ // terminal 的 crash 窗口同样由此收敛 (SPEC §30)。
1063
+ const ownerOverride = piTask ? planOwnerVerdictOverrideResume(piTask) : null;
1064
+ if (ownerOverride) {
1065
+ return await this.applyOwnerVerdictOverrideAndFinalize(taskId, leasedTask, ownerOverride);
1066
+ }
800
1067
  const intent = piTask?.completionIntent;
801
1068
  if (!piTask || !intent || intent.status !== 'pending') {
802
1069
  // P0 (INV-1/INV-5): applied 但任务未 terminal (标 applied 后、
@@ -925,6 +1192,117 @@ export class EvaluatorRunner extends BasePeerRunner {
925
1192
  attemptCount: leasedTask.attemptCount,
926
1193
  };
927
1194
  }
1195
+ /**
1196
+ * PRI-629: 应用 Owner verdict override 并收敛 terminal。
1197
+ *
1198
+ * 顺序 (SPEC §10/§30): 恢复 durable output → 幂等效果 (override decision)
1199
+ * → completion intent 标 applied → resolution 标 applied → markTaskSucceeded。
1200
+ * 任何 crash 窗口重放同一 resolution,不重新调用 LLM。机器 verdict
1201
+ * (runnerDecision) 永不改写。
1202
+ */
1203
+ async applyOwnerVerdictOverrideAndFinalize(taskId, leasedTask, plan) {
1204
+ const { resolution, overrideDecision } = plan;
1205
+ this.emitEvent('owner_resolution_applying', taskId, {
1206
+ resolutionId: resolution.resolutionId,
1207
+ action: resolution.action,
1208
+ machineDecision: resolution.machineDecision,
1209
+ effectiveDecision: overrideDecision,
1210
+ sourceRunId: resolution.sourceRunId,
1211
+ });
1212
+ // 恢复裁决时的 durable output — decision 必须与 resolution 记录的机器判定一致
1213
+ const output = await this.recoverIntentOutput(taskId, resolution.sourceRunId, resolution.machineDecision);
1214
+ // SPEC §20 纵深防御: 确定性对抗门失败不允许 approved override (capability
1215
+ // 层已挡一道;此处防事实漂移窗口)。
1216
+ if (overrideDecision === 'approved'
1217
+ && isEvaluatorOutputV2(output)
1218
+ && output.adversarialResult
1219
+ && output.adversarialResult.passed === false) {
1220
+ this.emitEvent('owner_resolution_rejected_by_policy', taskId, {
1221
+ resolutionId: resolution.resolutionId,
1222
+ reason: 'deterministic_hard_gate_failed',
1223
+ nextAction: 'owner_may_choose_revise_once_or_reject_current',
1224
+ });
1225
+ throw new PDRuntimeError('input_invalid', `Owner accept_current refused for task ${taskId}: deterministic adversarial gate failed (owner cannot override hard safety)`);
1226
+ }
1227
+ const artifactId = resolution.sourceArtifactId;
1228
+ const contextHash = `owner-override-${resolution.resolutionId}`;
1229
+ // rule assembly 输入由 durable lineage 重建 (与 intent resume 相同)
1230
+ const assemblySourceId = output.sourceArtificerArtifactId ?? null;
1231
+ let artificerContent = null;
1232
+ if (assemblySourceId) {
1233
+ try {
1234
+ const rec = await this.artifactStore.getArtifactById(assemblySourceId);
1235
+ artificerContent = rec?.contentJson ?? null;
1236
+ }
1237
+ catch {
1238
+ artificerContent = null;
1239
+ }
1240
+ }
1241
+ const effectResult = await this.applyEvaluatorDecisionEffects({
1242
+ taskId,
1243
+ runId: resolution.sourceRunId,
1244
+ finalOutput: output,
1245
+ task: leasedTask,
1246
+ artifactId,
1247
+ contextHash,
1248
+ sourceArtificerArtifactId: assemblySourceId,
1249
+ ruleAssemblyInput: { artificerArtifact: artificerContent, sourceArtificerArtifactId: assemblySourceId },
1250
+ decisionOverride: overrideDecision === 'approved' ? 'approved' : 'rejected',
1251
+ });
1252
+ if (effectResult.kind === 'human_review') {
1253
+ // P0 评审修复: 与 rollout 对称——override 驱动的 effects 落入 recovery
1254
+ // NHR 时,Owner 裁决已被执行,resolution 标 applied (否则 pending 残留
1255
+ // + Recover guard 拒绝 = 死胡同)。applied 后 Recover 放行,resume 门
1256
+ // 确定性重放。
1257
+ await markOwnerResolutionApplied({
1258
+ updateDiagnosticJson: (tid, json) => this.stateManager.updateTaskDiagnosticJson(tid, json),
1259
+ getTask: (tid) => this.stateManager.getTask(tid),
1260
+ taskId,
1261
+ resolutionId: resolution.resolutionId,
1262
+ appliedAt: new Date().toISOString(),
1263
+ });
1264
+ return effectResult.result;
1265
+ }
1266
+ await this.markCompletionIntentAppliedOrThrow(taskId);
1267
+ await markOwnerResolutionApplied({
1268
+ updateDiagnosticJson: (tid, json) => this.stateManager.updateTaskDiagnosticJson(tid, json),
1269
+ getTask: (tid) => this.stateManager.getTask(tid),
1270
+ taskId,
1271
+ resolutionId: resolution.resolutionId,
1272
+ appliedAt: new Date().toISOString(),
1273
+ });
1274
+ const resultRef = `${this.config.resultRefPrefix}://${resolution.sourceRunId}`;
1275
+ try {
1276
+ await this.stateManager.markTaskSucceeded(taskId, resultRef);
1277
+ }
1278
+ catch (stateErr) {
1279
+ this.emitEvent('mark_succeeded_failed', taskId, {
1280
+ taskId,
1281
+ runId: resolution.sourceRunId,
1282
+ errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
1283
+ });
1284
+ throw stateErr;
1285
+ }
1286
+ this.emitEvent('task_succeeded', taskId, {
1287
+ attemptCount: leasedTask.attemptCount,
1288
+ resultRef,
1289
+ evaluationDecision: output.evaluation.decision,
1290
+ evaluationScore: output.evaluation.score,
1291
+ ruleArtifactId: effectResult.ruleArtifactId,
1292
+ ownerResolutionApplied: resolution.resolutionId,
1293
+ effectiveDecision: overrideDecision,
1294
+ });
1295
+ return {
1296
+ status: 'succeeded',
1297
+ taskId,
1298
+ runId: resolution.sourceRunId,
1299
+ artifactId,
1300
+ resultRef,
1301
+ contextHash,
1302
+ output,
1303
+ attemptCount: leasedTask.attemptCount,
1304
+ };
1305
+ }
928
1306
  /**
929
1307
  * 从 runs 表恢复 intent 落库前已持久化的 validated output,并交叉核对
930
1308
  * decision 与 intent 一致 (authority 记录一致性)。intent 的存在保证
@@ -985,7 +1363,7 @@ export class EvaluatorRunner extends BasePeerRunner {
985
1363
  nextAction: 'owner_manual_review_required',
986
1364
  priorRepairIteration,
987
1365
  });
988
- return { kind: 'max_iterations_reached' };
1366
+ return { kind: 'max_iterations_reached', detail: 'budget_exhausted' };
989
1367
  }
990
1368
  // ── Slice 4: seed artificer repair task ──
991
1369
  // sourceArtificerArtifactId 由 caller 解析 (fresh: context ?? output;
@@ -997,7 +1375,7 @@ export class EvaluatorRunner extends BasePeerRunner {
997
1375
  runId: evaluatorRunId,
998
1376
  reason: 'source_artificer_artifact_id_unresolved',
999
1377
  });
1000
- return { kind: 'max_iterations_reached' };
1378
+ return { kind: 'max_iterations_reached', detail: 'seed_failed' };
1001
1379
  }
1002
1380
  // Construct the new repairPayload (repairIteration = prior + 1).
1003
1381
  // The 6 fields are sourced from the current evaluator output (rc-7:
@@ -1048,7 +1426,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1048
1426
  runId: evaluatorRunId,
1049
1427
  reason: 'seed_artificer_repair_task_not_injected',
1050
1428
  });
1051
- return { kind: 'max_iterations_reached' };
1429
+ return { kind: 'max_iterations_reached', detail: 'seed_failed' };
1052
1430
  }
1053
1431
  try {
1054
1432
  const repairTaskId = await this.repairTaskSeeder({
@@ -1074,7 +1452,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1074
1452
  // Treat as max iterations reached so the caller skips markTaskSucceeded
1075
1453
  // and returns succeeded — the evaluator verdict stands; only the repair
1076
1454
  // seeding failed, which is logged.
1077
- return { kind: 'max_iterations_reached' };
1455
+ return { kind: 'max_iterations_reached', detail: 'seed_failed' };
1078
1456
  }
1079
1457
  }
1080
1458
  // ── Optional hooks ──────────────────────────────────────────────────────────
@@ -1129,17 +1507,20 @@ export class EvaluatorRunner extends BasePeerRunner {
1129
1507
  }
1130
1508
  // ── PRI-426: adversarial sandbox replay ─────────────────────────────────────
1131
1509
  /**
1132
- * Run a single-round adversarial sandbox replay on the evaluator's V2 output
1133
- * (PRD Decision 11d). Pure orchestration of pure functions:
1510
+ * Run a single-round adversarial sandbox replay (PRD Decision 11d).
1511
+ * PRI-634 A2/R1: the `output` is accepted in V1 shape too — a code-bearing
1512
+ * Artificer artifact requires the gate regardless of whether the evaluator
1513
+ * LLM emitted optional V2 fields. Only V2-shaped outputs can contribute
1514
+ * LLM-supplied adversarialCases (checked via isEvaluatorOutputV2 below).
1515
+ * Pure orchestration of pure functions:
1134
1516
  * 1. Skip if passive review failed (decision !== 'approved' is the LLM's
1135
- * short-circuit signal — no code to defend).
1136
- * 2. Skip if no adversarialCases present (V2 with codeReview only).
1137
- * 3. Convert adversarialCases GoldenTrace (all negative, PRI-423).
1138
- * 4. Merge ≥1 positive case from the Artificer golden trace. If the
1517
+ * short-circuit signal — no code to defend). Now observable (R9).
1518
+ * 2. Convert adversarialCases GoldenTrace (all negative, PRI-423).
1519
+ * 3. Merge ≥1 positive case from the Artificer golden trace. If the
1139
1520
  * artificer artifact has no goldenTraceCases (V1 mismatch), degrade:
1140
1521
  * skip replay with telemetry — do NOT crash.
1141
- * 5. Invoke evaluateRefinerRuleHostGate via injected gateDeps.
1142
- * 6. Populate adversarialResult from the gate result.
1522
+ * 4. Invoke evaluateRefinerRuleHostGate via injected gateDeps.
1523
+ * 5. Populate adversarialResult from the gate result.
1143
1524
  *
1144
1525
  * Never throws — all failure modes degrade to a returned result with a
1145
1526
  * structured reason (ERR-018). The caller persists the updated output.
@@ -1151,15 +1532,29 @@ export class EvaluatorRunner extends BasePeerRunner {
1151
1532
  // eslint-disable-next-line @typescript-eslint/prefer-destructuring
1152
1533
  const gateDeps = this.gateDeps;
1153
1534
  if (!gateDeps) {
1154
- return { updatedOutput: null };
1535
+ // Defensive unreachable via the current caller (R2 fails loud before
1536
+ // this point). Kept observable so a future caller cannot silently
1537
+ // reintroduce the un-gated path (PRI-634 A3).
1538
+ this.emitEvent('adversarial_replay_skipped', taskId, {
1539
+ runId,
1540
+ reason: 'gate_deps_not_injected',
1541
+ nextAction: 'wire_gateDeps_createProductionGateDeps_into_evaluator_runner_assembly',
1542
+ });
1543
+ return { updatedOutput: null, skipReason: 'gate_deps_not_injected' };
1155
1544
  }
1156
1545
  // (1) Passive review short-circuit: the LLM emits decision='needs_revision'
1157
1546
  // when any of intentConsistency/scopePrecision/traceCoverage fails. Only
1158
1547
  // replay when the LLM judged the code worth defending. This check is
1159
- // defensive — by PRD contract the prompt instructs the LLM to short-circuit,
1160
- // but we don't trust the LLM to be the sole gate (Runtime Contract Rule 3).
1548
+ // defensive — the caller already gated on decision='approved' (PRI-634 A2
1549
+ // moved the check to the gate decision point); kept here so a future
1550
+ // caller cannot silently bypass it (Runtime Contract Rule 3).
1161
1551
  if (output.evaluation.decision !== 'approved') {
1162
- return { updatedOutput: null };
1552
+ this.emitEvent('adversarial_replay_skipped', taskId, {
1553
+ runId,
1554
+ reason: 'evaluation_not_approved',
1555
+ nextAction: 'repair_loop_or_next_review_round',
1556
+ });
1557
+ return { updatedOutput: null, skipReason: 'evaluation_not_approved' };
1163
1558
  }
1164
1559
  // (2) Resolve the Artificer artifact early — we need it both to derive
1165
1560
  // the v2 adversarial spec (PRI-485) and to merge positive cases (PRI-423).
@@ -1169,7 +1564,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1169
1564
  reason: 'no_artificer_artifact_in_context',
1170
1565
  nextAction: 'verify_buildContext_resolves_artificer_artifact',
1171
1566
  });
1172
- return { updatedOutput: null };
1567
+ return { updatedOutput: null, skipReason: 'no_artificer_artifact_in_context' };
1173
1568
  }
1174
1569
  const artificerParsed = this.parseArtificerArtifact(context.artificerArtifact);
1175
1570
  if (!artificerParsed) {
@@ -1178,7 +1573,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1178
1573
  reason: 'artificer_artifact_unparseable',
1179
1574
  nextAction: 'verify_artificer_artifact_contentJson',
1180
1575
  });
1181
- return { updatedOutput: null };
1576
+ return { updatedOutput: null, skipReason: 'artificer_artifact_unparseable' };
1182
1577
  }
1183
1578
  const { implementationCode, goldenTraceCases, affectedTools } = artificerParsed;
1184
1579
  if (typeof implementationCode !== 'string' || implementationCode.trim() === '') {
@@ -1189,7 +1584,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1189
1584
  reason: 'artificer_artifact_has_no_implementation_code',
1190
1585
  nextAction: 'verify_artificer_l2_adapter_emitted_v2',
1191
1586
  });
1192
- return { updatedOutput: null };
1587
+ return { updatedOutput: null, skipReason: 'artificer_artifact_has_no_implementation_code' };
1193
1588
  }
1194
1589
  // Merge positive cases from the Artificer golden trace into the adversarial
1195
1590
  // trace. buildGoldenTraceFromArtificer validates each case structurally
@@ -1201,20 +1596,36 @@ export class EvaluatorRunner extends BasePeerRunner {
1201
1596
  reason: 'no_positive_case_in_artificer_golden_trace',
1202
1597
  nextAction: 'verify_artificer_emitted_at_least_one_positive_case',
1203
1598
  });
1204
- return { updatedOutput: null };
1599
+ return { updatedOutput: null, skipReason: 'no_positive_case_in_artificer_golden_trace' };
1205
1600
  }
1206
1601
  // (3) PRI-485 Phase 6: auto-generate 5 v2 adversarial cases from the
1207
1602
  // Artificer's affectedTools + first positive case's path. These defend
1208
1603
  // against the most common false-positive patterns (unavailable/truncation/
1209
1604
  // alias/path/combination). Degrade with telemetry (rc-9) if the spec
1210
1605
  // cannot be derived — LLM-supplied adversarialCases still replay.
1211
- const llmCases = output.adversarialCases ?? [];
1606
+ // PRI-634 R1: V1-shaped outputs carry no adversarialCases the merged
1607
+ // set below then relies entirely on the auto-generated v2 cases.
1608
+ const llmCases = isEvaluatorOutputV2(output) ? (output.adversarialCases ?? []) : [];
1212
1609
  const v2Cases = this.generateV2CasesFromArtificer(affectedTools, positiveCases, taskId, runId);
1213
1610
  const mergedAdversarialCases = [...v2Cases, ...llmCases];
1214
1611
  // (4) No adversarial cases (neither v2-generated nor LLM-supplied) →
1215
1612
  // nothing to replay. codeReview may still be present (passive review only).
1613
+ // Previously a silent return (PRI-634 A3 hole #3); now observable (R9).
1614
+ //
1615
+ // PRI-634 R3: for a code-bearing Artificer artifact this is NOT a benign
1616
+ // degradation — it reproduces the chain-48371236 terminal state (approved
1617
+ // with no gate executed, adversarialResult=null, no pi-rule-* ever
1618
+ // emitted). The caller fails loud on this skipReason instead of reporting
1619
+ // succeeded. Non-code-bearing artifacts keep the legacy degrade (R1's
1620
+ // authority is the durable Artificer content, and only code-bearing
1621
+ // content can reach rule assembly).
1216
1622
  if (mergedAdversarialCases.length === 0) {
1217
- return { updatedOutput: null };
1623
+ this.emitEvent('adversarial_replay_skipped', taskId, {
1624
+ runId,
1625
+ reason: 'no_adversarial_cases_after_merge',
1626
+ nextAction: 'verify_artificer_affectedTools_or_positive_case_path_derivable',
1627
+ });
1628
+ return { updatedOutput: null, skipReason: 'no_adversarial_cases_after_merge' };
1218
1629
  }
1219
1630
  // (5) Convert the merged adversarial cases to an all-negative GoldenTrace.
1220
1631
  const conversion = adversarialCasesToGoldenTrace(mergedAdversarialCases);
@@ -1226,7 +1637,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1226
1637
  reason: `adversarial_conversion_failed: ${conversion.reason}`,
1227
1638
  nextAction: 'verify_adversarial_case_validator_alignment',
1228
1639
  });
1229
- return { updatedOutput: null };
1640
+ return { updatedOutput: null, skipReason: `adversarial_conversion_failed: ${conversion.reason}` };
1230
1641
  }
1231
1642
  const mergedTrace = {
1232
1643
  traceId: `golden-trace-evaluator-replay-${taskId}-${Date.now().toString(36)}`,
@@ -1255,6 +1666,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1255
1666
  };
1256
1667
  return {
1257
1668
  updatedOutput: { ...output, adversarialResult: failedResult },
1669
+ skipReason: null,
1258
1670
  };
1259
1671
  }
1260
1672
  this.emitEvent('adversarial_replay', taskId, {
@@ -1274,6 +1686,7 @@ export class EvaluatorRunner extends BasePeerRunner {
1274
1686
  };
1275
1687
  return {
1276
1688
  updatedOutput: { ...output, adversarialResult },
1689
+ skipReason: null,
1277
1690
  };
1278
1691
  }
1279
1692
  /**
@@ -1408,12 +1821,21 @@ export class EvaluatorRunner extends BasePeerRunner {
1408
1821
  });
1409
1822
  return [];
1410
1823
  }
1411
- const pathParam = firstPositive.params?.path;
1412
- if (typeof pathParam !== 'string' || pathParam.trim() === '') {
1824
+ // PRI-634: golden-trace params are echoed verbatim from the host
1825
+ // trajectory, and the dominant write-tool schema names its target
1826
+ // `file_path` (OpenClaw / Claude Code), not `path`. Reading only `path`
1827
+ // made v2 auto-generation structurally unreachable on real chains: the
1828
+ // merged case set stayed empty, so the gate never ran and an
1829
+ // LLM-declared adversarialResult.passed=true could stand in for a real
1830
+ // replay (chain 48371236). Accept both spellings — this is the input that
1831
+ // makes the deterministic gate reachable at all.
1832
+ const { params } = firstPositive;
1833
+ const pathParam = resolveCasePathParam(params);
1834
+ if (pathParam === null) {
1413
1835
  this.emitEvent('v2_adversarial_cases_skipped', taskId, {
1414
1836
  runId,
1415
1837
  reason: 'no_path_param_for_v2_adversarial_cases',
1416
- nextAction: 'verify_positive_case_has_string_path_param',
1838
+ nextAction: 'verify_positive_case_has_string_path_or_file_path_param',
1417
1839
  });
1418
1840
  return [];
1419
1841
  }