@principles/core 1.259.0 → 1.260.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime-v2/__tests__/activation-compatibility-read-model.test.d.ts +2 -0
- package/dist/runtime-v2/__tests__/activation-compatibility-read-model.test.d.ts.map +1 -0
- package/dist/runtime-v2/__tests__/activation-compatibility-read-model.test.js +148 -0
- package/dist/runtime-v2/__tests__/activation-compatibility-read-model.test.js.map +1 -0
- package/dist/runtime-v2/__tests__/architecture-regression.test.js +13 -3
- package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js +14 -6
- package/dist/runtime-v2/__tests__/evaluator-repair-loop.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js +1 -0
- package/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js +5 -0
- package/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/golden-trace.test.js +2 -2
- package/dist/runtime-v2/__tests__/golden-trace.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/internalization-contracts.test.js +4 -6
- package/dist/runtime-v2/__tests__/internalization-contracts.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/internalization-orchestrator.test.js +4 -1
- package/dist/runtime-v2/__tests__/internalization-orchestrator.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/internalization-state-machine.test.js +3 -2
- package/dist/runtime-v2/__tests__/internalization-state-machine.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js +47 -0
- package/dist/runtime-v2/__tests__/pain-to-principle-service.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/rollout-reviewer-runner-vslice.test.js +66 -1
- package/dist/runtime-v2/__tests__/rollout-reviewer-runner-vslice.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.d.ts +2 -0
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.d.ts.map +1 -0
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js +234 -0
- package/dist/runtime-v2/__tests__/rollout-reviewer-verdict-paths.test.js.map +1 -0
- package/dist/runtime-v2/__tests__/rule-host-evaluator.test.js +4 -4
- package/dist/runtime-v2/__tests__/rule-host-evaluator.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/story-a-demo.test.js +8 -8
- package/dist/runtime-v2/__tests__/story-a-demo.test.js.map +1 -1
- package/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js +23 -1
- package/dist/runtime-v2/activation/__tests__/prompt-activation-reader-contract.test.js.map +1 -1
- package/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts +11 -0
- package/dist/runtime-v2/activation/prompt-activation-reader-contract.d.ts.map +1 -1
- package/dist/runtime-v2/activation/prompt-activation-reader-contract.js +11 -4
- package/dist/runtime-v2/activation/prompt-activation-reader-contract.js.map +1 -1
- package/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js +31 -0
- package/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js.map +1 -1
- package/dist/runtime-v2/activation-compatibility-read-model.d.ts +24 -0
- package/dist/runtime-v2/activation-compatibility-read-model.d.ts.map +1 -0
- package/dist/runtime-v2/activation-compatibility-read-model.js +132 -0
- package/dist/runtime-v2/activation-compatibility-read-model.js.map +1 -0
- package/dist/runtime-v2/adapter/output-schema-registry.d.ts.map +1 -1
- package/dist/runtime-v2/adapter/output-schema-registry.js +2 -0
- package/dist/runtime-v2/adapter/output-schema-registry.js.map +1 -1
- package/dist/runtime-v2/config/__tests__/pd-validate-profile-legacy-keys.test.d.ts +13 -0
- package/dist/runtime-v2/config/__tests__/pd-validate-profile-legacy-keys.test.d.ts.map +1 -0
- package/dist/runtime-v2/config/__tests__/pd-validate-profile-legacy-keys.test.js +73 -0
- package/dist/runtime-v2/config/__tests__/pd-validate-profile-legacy-keys.test.js.map +1 -0
- package/dist/runtime-v2/config/pd-config-types.d.ts +0 -23
- package/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/dist/runtime-v2/config/pd-profile-constants.d.ts +11 -5
- package/dist/runtime-v2/config/pd-profile-constants.d.ts.map +1 -1
- package/dist/runtime-v2/config/pd-profile-constants.js +10 -45
- package/dist/runtime-v2/config/pd-profile-constants.js.map +1 -1
- package/dist/runtime-v2/config/pd-validate-profile.d.ts.map +1 -1
- package/dist/runtime-v2/config/pd-validate-profile.js +27 -135
- package/dist/runtime-v2/config/pd-validate-profile.js.map +1 -1
- package/dist/runtime-v2/core-principles/core-principle-registry.d.ts +4 -4
- package/dist/runtime-v2/core-principles/core-principle-registry.d.ts.map +1 -1
- package/dist/runtime-v2/core-principles/core-principle-registry.js +2 -2
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +7 -2
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js +9 -5
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/golden-trace-replay-validator.d.ts.map +1 -1
- package/dist/runtime-v2/golden-trace-replay-validator.js +0 -2
- package/dist/runtime-v2/golden-trace-replay-validator.js.map +1 -1
- package/dist/runtime-v2/golden-trace.d.ts.map +1 -1
- package/dist/runtime-v2/golden-trace.js +0 -3
- package/dist/runtime-v2/golden-trace.js.map +1 -1
- package/dist/runtime-v2/index.d.ts +16 -10
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +10 -7
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.js +157 -0
- package/dist/runtime-v2/internalization/__tests__/a-liveness-reconciliation.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/c2-live-runner-chain.test.js +29 -0
- package/dist/runtime-v2/internalization/__tests__/c2-live-runner-chain.test.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.js +311 -0
- package/dist/runtime-v2/internalization/__tests__/crash-liveness-regressions.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js +171 -0
- package/dist/runtime-v2/internalization/__tests__/external-review-p0-regressions.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/internalization-task-guards.test.js +11 -2
- package/dist/runtime-v2/internalization/__tests__/internalization-task-guards.test.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js +335 -0
- package/dist/runtime-v2/internalization/__tests__/internalization-transition-decision.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.js +101 -0
- package/dist/runtime-v2/internalization/__tests__/legacy-rule-contract-scanner.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.js +515 -0
- package/dist/runtime-v2/internalization/__tests__/mvp-core-loop-journeys.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.js +167 -0
- package/dist/runtime-v2/internalization/__tests__/restart-safety.e2e.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.js +601 -0
- package/dist/runtime-v2/internalization/__tests__/verdict-drift-regressions.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts +7 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +18 -2
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts +49 -0
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +390 -98
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/index.d.ts +2 -0
- package/dist/runtime-v2/internalization/index.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/index.js +1 -0
- package/dist/runtime-v2/internalization/index.js.map +1 -1
- package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +127 -0
- package/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/internalization-orchestrator.js +345 -1
- package/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
- package/dist/runtime-v2/internalization/internalization-task-guards.d.ts +9 -2
- package/dist/runtime-v2/internalization/internalization-task-guards.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/internalization-task-guards.js +14 -3
- package/dist/runtime-v2/internalization/internalization-task-guards.js.map +1 -1
- package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts +61 -0
- package/dist/runtime-v2/internalization/internalization-transition-decision.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/internalization-transition-decision.js +84 -0
- package/dist/runtime-v2/internalization/internalization-transition-decision.js.map +1 -0
- package/dist/runtime-v2/internalization/legacy-rule-contract-scanner.d.ts +51 -0
- package/dist/runtime-v2/internalization/legacy-rule-contract-scanner.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/legacy-rule-contract-scanner.js +81 -0
- package/dist/runtime-v2/internalization/legacy-rule-contract-scanner.js.map +1 -0
- package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +17 -1
- package/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
- package/dist/runtime-v2/internalization/pitask-metadata.d.ts +99 -2
- package/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/pitask-metadata.js +148 -0
- package/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
- package/dist/runtime-v2/internalization/refiner-sandbox-wrapper.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/refiner-sandbox-wrapper.js +0 -2
- package/dist/runtime-v2/internalization/refiner-sandbox-wrapper.js.map +1 -1
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts +147 -0
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.js +672 -1
- package/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/rule-host-contracts.d.ts +0 -3
- package/dist/runtime-v2/internalization/rule-host-contracts.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/rule-host-helpers.d.ts +4 -2
- package/dist/runtime-v2/internalization/rule-host-helpers.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/rule-host-helpers.js +4 -2
- package/dist/runtime-v2/internalization/rule-host-helpers.js.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.d.ts +2 -0
- package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.js +21 -1
- package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/dist/runtime-v2/pain-signal-observability.js +3 -3
- package/dist/runtime-v2/pain-signal-observability.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +9 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +22 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.d.ts +2 -0
- package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.d.ts.map +1 -0
- package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.js +130 -0
- package/dist/runtime-v2/signal-collector/__tests__/llm-payload-contract.test.js.map +1 -0
- package/dist/runtime-v2/signal-collector/index.d.ts +1 -1
- package/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
- package/dist/runtime-v2/signal-collector/index.js +1 -1
- package/dist/runtime-v2/signal-collector/index.js.map +1 -1
- package/dist/runtime-v2/signal-collector/llm-stage.d.ts +15 -0
- package/dist/runtime-v2/signal-collector/llm-stage.d.ts.map +1 -1
- package/dist/runtime-v2/signal-collector/llm-stage.js +28 -0
- package/dist/runtime-v2/signal-collector/llm-stage.js.map +1 -1
- package/dist/runtime-v2/signal-collector/types.d.ts +9 -1
- package/dist/runtime-v2/signal-collector/types.d.ts.map +1 -1
- package/dist/runtime-v2/signal-collector/types.js +13 -1
- package/dist/runtime-v2/signal-collector/types.js.map +1 -1
- package/dist/runtime-v2/store/reconciliation-cursor-store.d.ts +23 -0
- package/dist/runtime-v2/store/reconciliation-cursor-store.d.ts.map +1 -0
- package/dist/runtime-v2/store/reconciliation-cursor-store.js +34 -0
- package/dist/runtime-v2/store/reconciliation-cursor-store.js.map +1 -0
- package/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
- package/dist/runtime-v2/store/sqlite-connection.js +7 -0
- package/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
- package/dist/runtime-v2/store/task/memory-task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/memory-task-store.js +22 -0
- package/dist/runtime-v2/store/task/memory-task-store.js.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.js +20 -0
- package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
- package/dist/runtime-v2/store/task/task-store.d.ts +14 -0
- package/dist/runtime-v2/store/task/task-store.d.ts.map +1 -1
- package/dist/runtime-v2/story-a-demo.d.ts.map +1 -1
- package/dist/runtime-v2/story-a-demo.js +2 -0
- package/dist/runtime-v2/story-a-demo.js.map +1 -1
- package/dist/runtime-v2/tools/artificer-l2-tool-contract.d.ts.map +1 -1
- package/dist/runtime-v2/tools/artificer-l2-tool-contract.js +0 -4
- package/dist/runtime-v2/tools/artificer-l2-tool-contract.js.map +1 -1
- package/dist/runtime-v2/types/event-payload.d.ts +1 -20
- package/dist/runtime-v2/types/event-payload.d.ts.map +1 -1
- package/dist/runtime-v2/types/event-payload.js +0 -12
- package/dist/runtime-v2/types/event-payload.js.map +1 -1
- package/dist/runtime-v2/types/event-types.d.ts +3 -18
- package/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/dist/runtime-v2/types/event-types.js +0 -8
- package/dist/runtime-v2/types/event-types.js.map +1 -1
- package/dist/runtime-v2/types/index.d.ts +3 -3
- package/dist/runtime-v2/types/index.d.ts.map +1 -1
- package/dist/runtime-v2/types/index.js +2 -2
- package/dist/runtime-v2/types/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/runtime-v2/thinking-models/__tests__/thinking-models-policy.test.d.ts +0 -2
- package/dist/runtime-v2/thinking-models/__tests__/thinking-models-policy.test.d.ts.map +0 -1
- package/dist/runtime-v2/thinking-models/__tests__/thinking-models-policy.test.js +0 -149
- package/dist/runtime-v2/thinking-models/__tests__/thinking-models-policy.test.js.map +0 -1
- package/dist/runtime-v2/thinking-models/index.d.ts +0 -3
- package/dist/runtime-v2/thinking-models/index.d.ts.map +0 -1
- package/dist/runtime-v2/thinking-models/index.js +0 -3
- package/dist/runtime-v2/thinking-models/index.js.map +0 -1
- package/dist/runtime-v2/thinking-models/thinking-models-policy.d.ts +0 -73
- package/dist/runtime-v2/thinking-models/thinking-models-policy.d.ts.map +0 -1
- package/dist/runtime-v2/thinking-models/thinking-models-policy.js +0 -227
- package/dist/runtime-v2/thinking-models/thinking-models-policy.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PDRuntimeError } from '../error-categories.js';
|
|
2
|
-
import { hydratePITaskRecord } from './pitask-metadata.js';
|
|
2
|
+
import { hydratePITaskRecord, createPITaskDiagnosticJson, mergePITaskMetadata } from './pitask-metadata.js';
|
|
3
3
|
import { RunnerPhase } from '../runner/runner-phase.js';
|
|
4
4
|
import { RolloutReviewerPromptBuilder } from './rollout-reviewer-prompt-builder.js';
|
|
5
5
|
import { reconcileLineageEcho } from './peer-runner-contracts.js';
|
|
@@ -30,12 +30,16 @@ export class RolloutReviewerRunner {
|
|
|
30
30
|
eventEmitter;
|
|
31
31
|
validator;
|
|
32
32
|
artifactStore;
|
|
33
|
+
dispatchActivation;
|
|
34
|
+
reopenRevisionTarget;
|
|
33
35
|
constructor(deps, options) {
|
|
34
36
|
this.stateManager = deps.stateManager;
|
|
35
37
|
this.runtimeAdapter = deps.runtimeAdapter;
|
|
36
38
|
this.eventEmitter = deps.eventEmitter;
|
|
37
39
|
this.validator = deps.validator;
|
|
38
40
|
this.artifactStore = deps.artifactStore;
|
|
41
|
+
this.dispatchActivation = deps.dispatchActivation;
|
|
42
|
+
this.reopenRevisionTarget = deps.reopenRevisionTarget;
|
|
39
43
|
this.resolvedOptions = resolveRolloutReviewerRunnerOptions(options);
|
|
40
44
|
}
|
|
41
45
|
get currentPhase() {
|
|
@@ -80,7 +84,17 @@ export class RolloutReviewerRunner {
|
|
|
80
84
|
taskKind: 'rollout_reviewer',
|
|
81
85
|
attemptCount: leasedTask.attemptCount,
|
|
82
86
|
});
|
|
87
|
+
// P0 (verdict drift) — pending completion intent 是 recovery authority:
|
|
88
|
+
// 同 epoch 的 crash/retry 重跑必须 resume 其 effects,禁止重新调用 LLM
|
|
89
|
+
// (非确定性重问可能产生 approve/reject 漂移,与已发生的 activation /
|
|
90
|
+
// revision side effect 形成治理矛盾)。置于主 try 内:resume 的 transient
|
|
91
|
+
// 失败走 retryOrFail → retry_wait → 下一轮继续 resume。
|
|
83
92
|
try {
|
|
93
|
+
const resumed = await this.maybeResumePendingIntent(taskId, leasedTask);
|
|
94
|
+
if (resumed) {
|
|
95
|
+
this.phase = RunnerPhase.Completed;
|
|
96
|
+
return resumed;
|
|
97
|
+
}
|
|
84
98
|
const storeRunId = await this.resolveStoreRunId(taskId);
|
|
85
99
|
this.phase = RunnerPhase.BuildingContext;
|
|
86
100
|
const { contextHash, evaluatorArtifact, sourceEvaluatorArtifactId } = await this.buildContext(taskId);
|
|
@@ -136,6 +150,8 @@ export class RolloutReviewerRunner {
|
|
|
136
150
|
output,
|
|
137
151
|
task: leasedTask,
|
|
138
152
|
contextHash,
|
|
153
|
+
sourceEvaluatorArtifactId,
|
|
154
|
+
channel: hydratePITaskRecord(leasedTask)?.channel,
|
|
139
155
|
});
|
|
140
156
|
}
|
|
141
157
|
catch (error) {
|
|
@@ -365,6 +381,33 @@ export class RolloutReviewerRunner {
|
|
|
365
381
|
});
|
|
366
382
|
}
|
|
367
383
|
const resultRef = `rollout-reviewer://${ctx.runId}`;
|
|
384
|
+
// ── P0 (verdict drift): decision + completion intent 原子落库 ──
|
|
385
|
+
// 单次 metadata 写同时持久 verdict 与 completion intent — intent 存在即
|
|
386
|
+
// 证明该 verdict 的 output/run 均已 durable (updateRunOutput 与 artifact
|
|
387
|
+
// 写均在前)。同 epoch crash/retry 重跑经入口门 resume 此 intent,不再
|
|
388
|
+
// 调用 LLM。needs_revision 的 iteration 在此锁定,resume 据此继续同一轮。
|
|
389
|
+
const { decision } = ctx.output.review;
|
|
390
|
+
const recorded = await this.recordCompletionOrThrow(ctx.taskId, ctx.runId, decision);
|
|
391
|
+
if (recorded.kind === 'budget_exhausted') {
|
|
392
|
+
// P0-A: verdict + completion intent (effect=needs_human_review) 已
|
|
393
|
+
// durable;执行终态效果后标 applied — crash 窗口内 resume 会重写
|
|
394
|
+
// needs_human_review,绝不重问 LLM。
|
|
395
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, `rollout_revision_budget_exhausted_applied_${recorded.appliedCount}`);
|
|
396
|
+
await this.markCompletionIntentAppliedOrThrow(ctx.taskId);
|
|
397
|
+
this.phase = RunnerPhase.Completed;
|
|
398
|
+
return RolloutReviewerRunner.buildSucceededResult(ctx, artifactId, resultRef);
|
|
399
|
+
}
|
|
400
|
+
// ── P0-2 (外部复核): dispatch/routing 是 governance transition 的组成
|
|
401
|
+
// 部分,在 markTaskSucceeded 之前执行;transient 失败 throw → retry →
|
|
402
|
+
// 入口门 resume (幂等 effects,无 LLM 重问)。
|
|
403
|
+
const effect = await this.applyDecisionEffects(ctx, recorded.kind === 'intent' ? recorded.iteration : undefined);
|
|
404
|
+
if (effect.kind === 'human_review') {
|
|
405
|
+
// 任务已转入 needs_human_review — 不得再 markTaskSucceeded 覆盖
|
|
406
|
+
this.phase = RunnerPhase.Completed;
|
|
407
|
+
return RolloutReviewerRunner.buildSucceededResult(ctx, artifactId, resultRef);
|
|
408
|
+
}
|
|
409
|
+
// ── P0 invariant 5: intent APPLIED 后才允许 terminal ──
|
|
410
|
+
await this.markCompletionIntentAppliedOrThrow(ctx.taskId);
|
|
368
411
|
try {
|
|
369
412
|
await this.stateManager.markTaskSucceeded(ctx.taskId, resultRef);
|
|
370
413
|
}
|
|
@@ -381,8 +424,12 @@ export class RolloutReviewerRunner {
|
|
|
381
424
|
resultRef,
|
|
382
425
|
reviewDecision: ctx.output.review.decision,
|
|
383
426
|
reviewConfidence: ctx.output.review.confidence,
|
|
427
|
+
...(effect.dispatchArtifactId ? { dispatchedArtifactId: effect.dispatchArtifactId } : {}),
|
|
384
428
|
});
|
|
385
429
|
this.phase = RunnerPhase.Completed;
|
|
430
|
+
return RolloutReviewerRunner.buildSucceededResult(ctx, artifactId, resultRef);
|
|
431
|
+
}
|
|
432
|
+
static buildSucceededResult(ctx, artifactId, resultRef) {
|
|
386
433
|
return {
|
|
387
434
|
status: 'succeeded',
|
|
388
435
|
taskId: ctx.taskId,
|
|
@@ -394,6 +441,630 @@ export class RolloutReviewerRunner {
|
|
|
394
441
|
attemptCount: ctx.task.attemptCount,
|
|
395
442
|
};
|
|
396
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* P0-1 (外部复核): 解析 activation 候选 artifact — 与 review source 分离。
|
|
446
|
+
*
|
|
447
|
+
* 事实基线 (evaluator-runner 产物形状):
|
|
448
|
+
* - evaluator 任务名下存在 kind='principle' 但 **pending** 的 evaluation
|
|
449
|
+
* 输出 artifact (pi-art-<evalTask>-<run>),其 contentJson 是 EvaluatorOutputV1,
|
|
450
|
+
* 不含可激活的 principleId/text —— 不是合法 activation 目标。
|
|
451
|
+
* - prompt/defer_archive 渠道的合法目标 = **scribe** 的 validated principle
|
|
452
|
+
* artifact (evaluator approved 时被 updateValidationStatus 翻 validated)。
|
|
453
|
+
* - code_tool_hook 渠道的合法目标 = evaluator assemble 的 validated **rule**
|
|
454
|
+
* artifact (pi-rule-<evalTask>-<run>, V2 对抗通过)。
|
|
455
|
+
*
|
|
456
|
+
* 解析策略: 沿 dep 链 (rollout→evaluator→artificer→scribe) 收集各 source task
|
|
457
|
+
* 的 artifacts,按 channel 期望的 kind+validated 过滤;恰好一个候选才接受;
|
|
458
|
+
* 零候选或多个候选 (历史脏数据) 一律 unresolved → needs_human_review,
|
|
459
|
+
* 禁止 firstArtifact/created_at 猜测。
|
|
460
|
+
*/
|
|
461
|
+
async resolveActivationCandidate(ctx) {
|
|
462
|
+
const channel = ctx.channel ?? 'prompt';
|
|
463
|
+
const wantKind = channel === 'code_tool_hook' ? 'rule' : 'principle';
|
|
464
|
+
const chainIds = await this.collectLineageSourceTaskIds(ctx.taskId, [ctx.taskId], 0);
|
|
465
|
+
const matches = [];
|
|
466
|
+
for (const taskId of chainIds) {
|
|
467
|
+
// P0-1 加固: prompt/defer 渠道排除决策型 runner 名下的 artifacts —
|
|
468
|
+
// evaluator 名下的 principle 是评审输出(即使历史脏数据把它翻成
|
|
469
|
+
// validated 也不是可激活的原则文本;合法 bearer 属于 scribe)。
|
|
470
|
+
if (wantKind === 'principle') {
|
|
471
|
+
const ownerTask = await this.stateManager.getTask(taskId);
|
|
472
|
+
if (ownerTask?.taskKind === 'evaluator' || ownerTask?.taskKind === 'rollout_reviewer') {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const artifacts = await this.artifactStore.listBySourceTaskId(taskId).catch(() => null);
|
|
477
|
+
if (!artifacts)
|
|
478
|
+
continue;
|
|
479
|
+
for (const a of artifacts) {
|
|
480
|
+
if (a.artifactKind === wantKind && a.validationStatus === 'validated') {
|
|
481
|
+
matches.push(a.artifactId);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (matches.length === 1) {
|
|
486
|
+
const [candidateId] = matches;
|
|
487
|
+
this.emitRolloutReviewerEvent('rollout_activation_candidate_resolved', ctx.taskId, {
|
|
488
|
+
artifactId: candidateId ?? '',
|
|
489
|
+
channel,
|
|
490
|
+
kind: wantKind,
|
|
491
|
+
searchedTaskIds: chainIds,
|
|
492
|
+
});
|
|
493
|
+
return candidateId ?? null;
|
|
494
|
+
}
|
|
495
|
+
this.emitRolloutReviewerEvent('rollout_activation_candidate_unresolved', ctx.taskId, {
|
|
496
|
+
channel,
|
|
497
|
+
kind: wantKind,
|
|
498
|
+
matchCount: matches.length,
|
|
499
|
+
matchedArtifactIds: matches,
|
|
500
|
+
reason: matches.length === 0 ? 'no_validated_candidate_in_lineage' : 'ambiguous_validated_candidates',
|
|
501
|
+
nextAction: 'inspect lineage artifacts; dispatch manually via pd activation dispatch after fixing',
|
|
502
|
+
});
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
/** 收集 lineage 上所有 source task id (BFS 沿 dependencyTaskIds, 有界深度)。 */
|
|
506
|
+
static LINEAGE_MAX_DEPTH = 5;
|
|
507
|
+
async collectLineageSourceTaskIds(rootTaskId, acc, depth) {
|
|
508
|
+
if (depth > RolloutReviewerRunner.LINEAGE_MAX_DEPTH)
|
|
509
|
+
return acc;
|
|
510
|
+
const task = await this.stateManager.getTask(rootTaskId);
|
|
511
|
+
if (!task)
|
|
512
|
+
return acc;
|
|
513
|
+
const piTask = hydratePITaskRecord(task);
|
|
514
|
+
for (const depId of piTask?.dependencyTaskIds ?? []) {
|
|
515
|
+
if (acc.includes(depId))
|
|
516
|
+
continue;
|
|
517
|
+
acc.push(depId);
|
|
518
|
+
await this.collectLineageSourceTaskIds(depId, acc, depth + 1);
|
|
519
|
+
}
|
|
520
|
+
return acc;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* P0-2 (外部复核): dispatch + 结果分类。
|
|
524
|
+
* 返回 true = governance transition 完成 (activated / already_activated /
|
|
525
|
+
* queued_for_approval);false = 已转入 needs_human_review。
|
|
526
|
+
* transient 异常直接冒泡 (caller → retryOrFail → retry_wait 自动重试)。
|
|
527
|
+
*/
|
|
528
|
+
async dispatchOrRouteFailure(ctx, candidateArtifactId) {
|
|
529
|
+
if (!this.dispatchActivation) {
|
|
530
|
+
this.emitRolloutReviewerEvent('rollout_dispatch_not_wired', ctx.taskId, {
|
|
531
|
+
reason: 'dispatch_activation_dep_not_injected',
|
|
532
|
+
nextAction: 'wire_activation_dispatcher_in_auto_consumer_or_run_once',
|
|
533
|
+
});
|
|
534
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, 'rollout_dispatch_not_wired');
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
// throw 在此冒泡: transient (db locked / network) → retry_wait,由消费循环重试
|
|
538
|
+
const outcome = await this.dispatchActivation({
|
|
539
|
+
artifactId: candidateArtifactId,
|
|
540
|
+
channel: ctx.channel ?? 'prompt',
|
|
541
|
+
confidence: ctx.output.review.confidence,
|
|
542
|
+
rolloutTaskId: ctx.taskId,
|
|
543
|
+
});
|
|
544
|
+
const completed = outcome.decision === 'activated'
|
|
545
|
+
|| outcome.decision === 'already_activated'
|
|
546
|
+
|| outcome.decision === 'queued_for_approval';
|
|
547
|
+
this.emitRolloutReviewerEvent(completed ? 'rollout_activation_dispatched' : 'rollout_activation_dispatch_refused', ctx.taskId, {
|
|
548
|
+
artifactId: candidateArtifactId,
|
|
549
|
+
channel: ctx.channel ?? 'prompt',
|
|
550
|
+
decision: outcome.decision,
|
|
551
|
+
activationId: outcome.activationId ?? null,
|
|
552
|
+
reason: outcome.reason ?? null,
|
|
553
|
+
nextAction: completed ? null : 'owner_inspect_artifact_then_manual_dispatch_or_archive',
|
|
554
|
+
});
|
|
555
|
+
if (!completed) {
|
|
556
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, `rollout_dispatch_${outcome.decision}`);
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* P0-E: needs_revision → reopen 最近可修复 stage (INV-04, 禁止入 approval)。
|
|
563
|
+
* 路由: code_tool_hook → artificer (规则实现); 其他 channel → scribe (原则措辞)。
|
|
564
|
+
*
|
|
565
|
+
* P0 (verdict drift): iteration 由 completion intent 注入 — record 阶段已按
|
|
566
|
+
* "已 APPLIED 的 rolloutRevisionPayload" 锁定本轮 N。同一 completion 的
|
|
567
|
+
* fresh 执行与 crash resume 使用同一 N,消除"applied 载荷属于上一轮还是
|
|
568
|
+
* 本轮"的歧义;budget 判定不在本方法内。
|
|
569
|
+
*
|
|
570
|
+
* 返回 true = revision transition 完成 (target 已 reopen);false = 任务已
|
|
571
|
+
* 转入 needs_human_review。transient 错误 throw → retryOrFail → retry_wait
|
|
572
|
+
* → 入口门 resume 同一 completion (无 LLM 重问)。
|
|
573
|
+
*/
|
|
574
|
+
async handleRevisionRouting(ctx, iteration) {
|
|
575
|
+
const target = await this.resolveRevisionTarget(ctx.taskId, ctx.channel ?? 'prompt');
|
|
576
|
+
if (!target) {
|
|
577
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, 'rollout_revision_target_unresolved');
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
if (!this.reopenRevisionTarget) {
|
|
581
|
+
this.emitRolloutReviewerEvent('rollout_revision_not_wired', ctx.taskId, {
|
|
582
|
+
reason: 'reopen_revision_target_dep_not_injected',
|
|
583
|
+
nextAction: 'wire_revision_routing_in_auto_consumer_or_run_once',
|
|
584
|
+
});
|
|
585
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, 'rollout_revision_routing_not_wired');
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
const feedback = RolloutReviewerRunner.formatRevisionFeedback(ctx.output);
|
|
589
|
+
// intent 载荷先持久化 (status=pending, N): 失败 = transient 存储错误 →
|
|
590
|
+
// throw (B4: 无 reopen、budget 不增;重试经入口门 resume,不重问 LLM)。
|
|
591
|
+
await this.recordRolloutRevisionRoutingOrThrow(ctx.taskId, {
|
|
592
|
+
requiredChanges: [...ctx.output.review.requiredChanges],
|
|
593
|
+
revisionIteration: iteration,
|
|
594
|
+
sourceRolloutTaskId: ctx.taskId,
|
|
595
|
+
sourceArtifactId: target.viaArtifactId ?? ctx.taskId,
|
|
596
|
+
targetTaskKind: target.kind,
|
|
597
|
+
status: 'pending',
|
|
598
|
+
});
|
|
599
|
+
try {
|
|
600
|
+
const causeId = `rollout-${ctx.taskId}-r${iteration}`;
|
|
601
|
+
// materialize 检查: target 已携带本 cause → 这轮 reopen 已发生过
|
|
602
|
+
// (crash before rollout succeeded 的重放) — 不得再次 reopen
|
|
603
|
+
const targetTask = await this.stateManager.getTask(target.taskId);
|
|
604
|
+
const targetPi = targetTask ? hydratePITaskRecord(targetTask) : null;
|
|
605
|
+
if (targetPi && targetPi.revisionCauseId === causeId) {
|
|
606
|
+
this.emitRolloutReviewerEvent('rollout_revision_already_materialized', ctx.taskId, {
|
|
607
|
+
targetTaskId: target.taskId,
|
|
608
|
+
revisionIteration: iteration,
|
|
609
|
+
causeId,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
const outcome = await this.reopenRevisionTarget({
|
|
614
|
+
targetTaskId: target.taskId,
|
|
615
|
+
targetKind: target.kind,
|
|
616
|
+
revisionFeedback: feedback,
|
|
617
|
+
revisionIteration: iteration,
|
|
618
|
+
sourceRolloutTaskId: ctx.taskId,
|
|
619
|
+
sourceArtifactId: target.viaArtifactId ?? ctx.taskId,
|
|
620
|
+
});
|
|
621
|
+
if (!outcome.ok) {
|
|
622
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, `rollout_revision_reopen_failed_${outcome.reason}`);
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// transition materialized → 载荷标 applied (写失败 fail loud,
|
|
627
|
+
// 重放经上面的 materialize 检查安全收敛)
|
|
628
|
+
await this.markRevisionIntentAppliedOrThrow(ctx.taskId, iteration, {
|
|
629
|
+
requiredChanges: [...ctx.output.review.requiredChanges],
|
|
630
|
+
sourceRolloutTaskId: ctx.taskId,
|
|
631
|
+
sourceArtifactId: target.viaArtifactId ?? ctx.taskId,
|
|
632
|
+
targetTaskKind: target.kind,
|
|
633
|
+
});
|
|
634
|
+
this.emitRolloutReviewerEvent('rollout_revision_routed', ctx.taskId, {
|
|
635
|
+
targetTaskId: target.taskId,
|
|
636
|
+
targetKind: target.kind,
|
|
637
|
+
revisionIteration: iteration,
|
|
638
|
+
});
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
this.emitRolloutReviewerEvent('rollout_revision_route_failed', ctx.taskId, {
|
|
643
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
644
|
+
nextAction: 'task_will_retry_then_resume_completion_intent_without_llm',
|
|
645
|
+
});
|
|
646
|
+
throw err;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 解析修订目标任务 (只读遍历 dep 链):
|
|
651
|
+
* rollout → evaluator → artificer → scribe
|
|
652
|
+
* code_tool_hook → artificer; 其他 → scribe (走到底)。
|
|
653
|
+
*/
|
|
654
|
+
async resolveRevisionTarget(taskId, channel) {
|
|
655
|
+
const rawTask = await this.stateManager.getTask(taskId);
|
|
656
|
+
if (!rawTask)
|
|
657
|
+
return null;
|
|
658
|
+
const piTask = hydratePITaskRecord(rawTask);
|
|
659
|
+
// 第一跳: rollout deps → evaluator; evaluator deps → artificer
|
|
660
|
+
let artificerTaskId = null;
|
|
661
|
+
let artificerDeps = [];
|
|
662
|
+
const firstHop = piTask?.dependencyTaskIds ?? [];
|
|
663
|
+
for (const depId of firstHop) {
|
|
664
|
+
const dep = await this.stateManager.getTask(depId);
|
|
665
|
+
if (!dep)
|
|
666
|
+
continue;
|
|
667
|
+
if (dep.taskKind === 'artificer') {
|
|
668
|
+
const depPi = hydratePITaskRecord(dep);
|
|
669
|
+
artificerTaskId = dep.taskId;
|
|
670
|
+
artificerDeps = depPi?.dependencyTaskIds ?? [];
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
if (dep.taskKind === 'evaluator') {
|
|
674
|
+
const evalPi = hydratePITaskRecord(dep);
|
|
675
|
+
for (const evalDepId of evalPi?.dependencyTaskIds ?? []) {
|
|
676
|
+
const evalDep = await this.stateManager.getTask(evalDepId);
|
|
677
|
+
if (!evalDep || evalDep.taskKind !== 'artificer')
|
|
678
|
+
continue;
|
|
679
|
+
const evalDepPi = hydratePITaskRecord(evalDep);
|
|
680
|
+
artificerTaskId = evalDep.taskId;
|
|
681
|
+
artificerDeps = evalDepPi?.dependencyTaskIds ?? [];
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
if (artificerTaskId)
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (!artificerTaskId)
|
|
689
|
+
return null;
|
|
690
|
+
if (channel === 'code_tool_hook') {
|
|
691
|
+
return { taskId: artificerTaskId, kind: 'artificer' };
|
|
692
|
+
}
|
|
693
|
+
for (const depId of artificerDeps) {
|
|
694
|
+
const dep = await this.stateManager.getTask(depId);
|
|
695
|
+
if (!dep || dep.taskKind !== 'scribe')
|
|
696
|
+
continue;
|
|
697
|
+
return { taskId: dep.taskId, kind: 'scribe' };
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
static formatRevisionFeedback(output) {
|
|
702
|
+
const lines = ['Rollout review 判定 needs_revision,请修订后重新走验证链:'];
|
|
703
|
+
for (const change of output.review.requiredChanges) {
|
|
704
|
+
lines.push(`- 必须修改: ${change}`);
|
|
705
|
+
}
|
|
706
|
+
for (const risk of output.review.rolloutRisks) {
|
|
707
|
+
lines.push(`- 风险: ${risk}`);
|
|
708
|
+
}
|
|
709
|
+
return lines.join('\n');
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* P0 (INV-2): needs_human_review 是 completion effect 的 materialize 操作 —
|
|
713
|
+
* fail-closed。写失败/读回不一致必须 throw (→ retry_wait → 入口门 resume
|
|
714
|
+
* 同一 effect,不问 LLM),禁止 catch+swallow 后让 caller 把 intent 标
|
|
715
|
+
* applied (intent applied ⇔ 其 durable effect 已 materialize)。
|
|
716
|
+
*/
|
|
717
|
+
async markNeedsHumanReviewOrThrow(taskId, reason) {
|
|
718
|
+
this.emitRolloutReviewerEvent('rollout_reviewer_task_needs_human_review', taskId, {
|
|
719
|
+
reason,
|
|
720
|
+
nextAction: 'owner_inspect_retry_or_archive',
|
|
721
|
+
});
|
|
722
|
+
try {
|
|
723
|
+
await this.stateManager.updateTask(taskId, { status: 'needs_human_review' });
|
|
724
|
+
// read-back invariant (INV-2): 只有 effect durable 才允许 caller 标 applied
|
|
725
|
+
const current = await this.stateManager.getTask(taskId);
|
|
726
|
+
if (!current || current.status !== 'needs_human_review') {
|
|
727
|
+
throw new PDRuntimeError('storage_unavailable', `needs_human_review effect not durable for task ${taskId} (read-back status: ${current?.status ?? 'task_missing'})`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
this.emitRolloutReviewerEvent('rollout_mark_human_review_failed', taskId, {
|
|
732
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
733
|
+
reason,
|
|
734
|
+
nextAction: 'task_will_retry_then_resume_completion_intent_without_llm',
|
|
735
|
+
});
|
|
736
|
+
throw err;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/** B: 读取已持久化的 revision intent (无 → null;旧形状缺 status 视为 applied) */
|
|
740
|
+
async readRolloutRevisionPayload(taskId) {
|
|
741
|
+
const raw = await this.stateManager.getTask(taskId);
|
|
742
|
+
if (!raw)
|
|
743
|
+
return null;
|
|
744
|
+
const piTask = hydratePITaskRecord(raw);
|
|
745
|
+
return piTask?.rolloutRevisionPayload ?? null;
|
|
746
|
+
}
|
|
747
|
+
// ── P0 (verdict drift): completion intent — record / effects / applied / resume ──
|
|
748
|
+
/**
|
|
749
|
+
* verdict + completion intent 原子落库 (单次 metadata 写)。
|
|
750
|
+
* needs_revision 同时锁定本轮 iteration:
|
|
751
|
+
* - stored payload pending → 沿用其 iteration (同 epoch 在途修订继续,
|
|
752
|
+
* 兼容旧形状 crash 态,B1);
|
|
753
|
+
* - stored applied → iteration + 1;appliedCount ≥ 2 → budget_exhausted
|
|
754
|
+
* (P0-A: 也必须记 completion intent,effect=needs_human_review —
|
|
755
|
+
* decision durable 而 crash before needs_human_review 写入时,retry
|
|
756
|
+
* 不得重问 LLM 漂移 verdict)。
|
|
757
|
+
*/
|
|
758
|
+
async recordCompletionOrThrow(taskId, runId, decision) {
|
|
759
|
+
const stored = await this.readRolloutRevisionPayload(taskId);
|
|
760
|
+
let iteration;
|
|
761
|
+
let budgetExhausted = false;
|
|
762
|
+
let appliedCount = 0;
|
|
763
|
+
if (decision === 'needs_revision') {
|
|
764
|
+
appliedCount = stored && stored.status !== 'pending' ? stored.revisionIteration : 0;
|
|
765
|
+
if (appliedCount >= 2) {
|
|
766
|
+
budgetExhausted = true;
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
iteration = stored && stored.status === 'pending' ? stored.revisionIteration : appliedCount + 1;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
const raw = await this.stateManager.getTask(taskId);
|
|
774
|
+
if (!raw)
|
|
775
|
+
throw new Error(`task ${taskId} not found`);
|
|
776
|
+
const pi = hydratePITaskRecord(raw);
|
|
777
|
+
if (!pi)
|
|
778
|
+
throw new Error(`task ${taskId} not hydratable`);
|
|
779
|
+
const merged = mergePITaskMetadata(pi, {
|
|
780
|
+
runnerDecision: decision === 'approve_rollout' || decision === 'needs_revision' || decision === 'reject'
|
|
781
|
+
? decision
|
|
782
|
+
: pi.runnerDecision,
|
|
783
|
+
completionIntent: {
|
|
784
|
+
decision: decision,
|
|
785
|
+
sourceRunId: runId,
|
|
786
|
+
revisionEpoch: pi.revisionCount ?? 0,
|
|
787
|
+
status: 'pending',
|
|
788
|
+
...(iteration !== undefined ? { revisionIteration: iteration } : {}),
|
|
789
|
+
...(budgetExhausted ? { effect: 'needs_human_review' } : {}),
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
await this.stateManager.updateTaskDiagnosticJson(taskId, createPITaskDiagnosticJson(merged));
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
this.emitRolloutReviewerEvent('rollout_completion_record_failed', taskId, {
|
|
796
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
797
|
+
nextAction: 'task_will_retry; record is idempotent overwrite',
|
|
798
|
+
});
|
|
799
|
+
throw err;
|
|
800
|
+
}
|
|
801
|
+
if (budgetExhausted) {
|
|
802
|
+
return { kind: 'budget_exhausted', appliedCount };
|
|
803
|
+
}
|
|
804
|
+
return { kind: 'intent', iteration };
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* 执行 decision 的治理效果 (fresh 与 resume 共用,幂等):
|
|
808
|
+
* approve → activation dispatch (幂等 key,重放 already_activated);
|
|
809
|
+
* needs_revision → revision routing (iteration 由 intent 注入);
|
|
810
|
+
* reject → 无效果 (terminal)。
|
|
811
|
+
*/
|
|
812
|
+
async applyDecisionEffects(ctx, iteration) {
|
|
813
|
+
if (ctx.output.review.decision === 'approve_rollout') {
|
|
814
|
+
const candidate = await this.resolveActivationCandidate(ctx);
|
|
815
|
+
if (!candidate) {
|
|
816
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, 'rollout_activation_candidate_unresolved');
|
|
817
|
+
return { kind: 'human_review' };
|
|
818
|
+
}
|
|
819
|
+
const completed = await this.dispatchOrRouteFailure(ctx, candidate);
|
|
820
|
+
return completed ? { kind: 'completed', dispatchArtifactId: candidate } : { kind: 'human_review' };
|
|
821
|
+
}
|
|
822
|
+
if (ctx.output.review.decision === 'needs_revision') {
|
|
823
|
+
if (iteration === undefined) {
|
|
824
|
+
// intent 缺 iteration 却走到 needs_revision 效果 — authority 记录损坏
|
|
825
|
+
await this.markNeedsHumanReviewOrThrow(ctx.taskId, 'rollout_revision_iteration_missing');
|
|
826
|
+
return { kind: 'human_review' };
|
|
827
|
+
}
|
|
828
|
+
const completed = await this.handleRevisionRouting(ctx, iteration);
|
|
829
|
+
return completed ? { kind: 'completed' } : { kind: 'human_review' };
|
|
830
|
+
}
|
|
831
|
+
// 'reject' → terminal: 无 dispatch、无 approval、无后继 (INV-04)
|
|
832
|
+
return { kind: 'completed' };
|
|
833
|
+
}
|
|
834
|
+
/** intent APPLIED 后才允许 terminal (P0 invariant 5)。写失败 fail loud。 */
|
|
835
|
+
async markCompletionIntentAppliedOrThrow(taskId) {
|
|
836
|
+
try {
|
|
837
|
+
const raw = await this.stateManager.getTask(taskId);
|
|
838
|
+
if (!raw)
|
|
839
|
+
throw new Error(`task ${taskId} not found`);
|
|
840
|
+
const pi = hydratePITaskRecord(raw);
|
|
841
|
+
if (!pi?.completionIntent)
|
|
842
|
+
return; // budget_exhausted 路径无 intent
|
|
843
|
+
const merged = mergePITaskMetadata(pi, {
|
|
844
|
+
completionIntent: { ...pi.completionIntent, status: 'applied' },
|
|
845
|
+
});
|
|
846
|
+
await this.stateManager.updateTaskDiagnosticJson(taskId, createPITaskDiagnosticJson(merged));
|
|
847
|
+
}
|
|
848
|
+
catch (err) {
|
|
849
|
+
this.emitRolloutReviewerEvent('rollout_completion_mark_applied_failed', taskId, {
|
|
850
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
851
|
+
nextAction: 'task_will_retry; resume re-passes materialize checks',
|
|
852
|
+
});
|
|
853
|
+
throw err;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* 入口恢复门: pending completion intent (同 epoch) 是 recovery authority。
|
|
858
|
+
* 返回非 null = 本次 run 以 resume 完成 (LLM 未被调用);
|
|
859
|
+
* 返回 null = 走正常 LLM 管线。epoch 不匹配的残留 intent 视为 stale。
|
|
860
|
+
*/
|
|
861
|
+
async maybeResumePendingIntent(taskId, leasedTask) {
|
|
862
|
+
const piTask = hydratePITaskRecord(leasedTask);
|
|
863
|
+
const intent = piTask?.completionIntent;
|
|
864
|
+
if (!piTask || !intent || intent.status !== 'pending') {
|
|
865
|
+
// P0 (INV-1/INV-5): applied 但任务未 terminal (标 applied 后、
|
|
866
|
+
// markTaskSucceeded 写失败/crash 的窗口) — effects 已 materialize,
|
|
867
|
+
// 不重问 LLM,直接补 terminal,消除 "applied+非终态" 的 LLM 重开窗口。
|
|
868
|
+
if (piTask && intent && intent.status === 'applied'
|
|
869
|
+
&& intent.revisionEpoch === (piTask.revisionCount ?? 0)
|
|
870
|
+
&& leasedTask.status !== 'needs_human_review') {
|
|
871
|
+
return await this.finalizeAppliedIntentTerminal(taskId, intent.sourceRunId, leasedTask);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
if (intent.revisionEpoch !== (piTask.revisionCount ?? 0)) {
|
|
876
|
+
this.emitRolloutReviewerEvent('rollout_completion_intent_stale_epoch', taskId, {
|
|
877
|
+
intentEpoch: intent.revisionEpoch,
|
|
878
|
+
currentEpoch: piTask.revisionCount ?? 0,
|
|
879
|
+
nextAction: 'reopen should have cleared intent; verify reopenTaskForRevision path',
|
|
880
|
+
});
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
this.emitRolloutReviewerEvent('rollout_completion_intent_resumed', taskId, {
|
|
884
|
+
decision: intent.decision,
|
|
885
|
+
sourceRunId: intent.sourceRunId,
|
|
886
|
+
});
|
|
887
|
+
// P0-A: needs_human_review 效果 (budget exhausted 等) 的 resume —
|
|
888
|
+
// 恢复 durable output (intent 落库前已持久),重写终态人工裁决状态,
|
|
889
|
+
// 不路由、不 dispatch、不问 LLM。
|
|
890
|
+
if (intent.effect === 'needs_human_review') {
|
|
891
|
+
const budgetOutput = await this.recoverIntentOutput(taskId, intent.sourceRunId);
|
|
892
|
+
const stored = await this.readRolloutRevisionPayload(taskId);
|
|
893
|
+
const appliedCount = stored && stored.status !== 'pending' ? stored.revisionIteration : 2;
|
|
894
|
+
await this.markNeedsHumanReviewOrThrow(taskId, `rollout_revision_budget_exhausted_applied_${appliedCount}`);
|
|
895
|
+
await this.markCompletionIntentAppliedOrThrow(taskId);
|
|
896
|
+
this.phase = RunnerPhase.Completed;
|
|
897
|
+
return RolloutReviewerRunner.buildResumeResult({
|
|
898
|
+
taskId,
|
|
899
|
+
runId: intent.sourceRunId,
|
|
900
|
+
artifactId: `pi-art-${taskId}-${intent.sourceRunId}`,
|
|
901
|
+
resultRef: `rollout-reviewer://${intent.sourceRunId}`,
|
|
902
|
+
output: budgetOutput,
|
|
903
|
+
attemptCount: leasedTask.attemptCount,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
const output = await this.recoverIntentOutput(taskId, intent.sourceRunId);
|
|
907
|
+
const ctx = {
|
|
908
|
+
taskId,
|
|
909
|
+
runId: intent.sourceRunId,
|
|
910
|
+
output,
|
|
911
|
+
task: leasedTask,
|
|
912
|
+
contextHash: `resume-${intent.sourceRunId}`,
|
|
913
|
+
sourceEvaluatorArtifactId: output.sourceEvaluatorArtifactId,
|
|
914
|
+
channel: piTask.channel,
|
|
915
|
+
};
|
|
916
|
+
const artifactId = `pi-art-${taskId}-${intent.sourceRunId}`;
|
|
917
|
+
const resultRef = `rollout-reviewer://${intent.sourceRunId}`;
|
|
918
|
+
const effect = await this.applyDecisionEffects(ctx, intent.revisionIteration);
|
|
919
|
+
if (effect.kind === 'human_review') {
|
|
920
|
+
this.phase = RunnerPhase.Completed;
|
|
921
|
+
return RolloutReviewerRunner.buildResumeResult({ taskId, runId: intent.sourceRunId, artifactId, resultRef, output, attemptCount: leasedTask.attemptCount });
|
|
922
|
+
}
|
|
923
|
+
await this.markCompletionIntentAppliedOrThrow(taskId);
|
|
924
|
+
try {
|
|
925
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
926
|
+
}
|
|
927
|
+
catch (stateErr) {
|
|
928
|
+
this.emitRolloutReviewerEvent('rollout_reviewer_mark_succeeded_failed', taskId, {
|
|
929
|
+
taskId,
|
|
930
|
+
runId: intent.sourceRunId,
|
|
931
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
932
|
+
});
|
|
933
|
+
throw stateErr;
|
|
934
|
+
}
|
|
935
|
+
this.emitRolloutReviewerEvent('rollout_reviewer_task_succeeded', taskId, {
|
|
936
|
+
attemptCount: leasedTask.attemptCount,
|
|
937
|
+
resultRef,
|
|
938
|
+
reviewDecision: intent.decision,
|
|
939
|
+
reviewConfidence: output.review.confidence,
|
|
940
|
+
resumedFromCompletionIntent: true,
|
|
941
|
+
...(effect.dispatchArtifactId ? { dispatchedArtifactId: effect.dispatchArtifactId } : {}),
|
|
942
|
+
});
|
|
943
|
+
this.phase = RunnerPhase.Completed;
|
|
944
|
+
return RolloutReviewerRunner.buildResumeResult({ taskId, runId: intent.sourceRunId, artifactId, resultRef, output, attemptCount: leasedTask.attemptCount });
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* P0 (INV-1/INV-5): applied intent 的补 terminal — effects 已 materialize
|
|
948
|
+
* (applied ⇒ INV-2 保证),仅 markTaskSucceeded 缺失。不调用 LLM。
|
|
949
|
+
*/
|
|
950
|
+
async finalizeAppliedIntentTerminal(taskId, sourceRunId, leasedTask) {
|
|
951
|
+
this.emitRolloutReviewerEvent('rollout_completion_intent_finalize_terminal', taskId, {
|
|
952
|
+
sourceRunId,
|
|
953
|
+
taskStatus: leasedTask.status,
|
|
954
|
+
});
|
|
955
|
+
const output = await this.recoverIntentOutput(taskId, sourceRunId);
|
|
956
|
+
const resultRef = `rollout-reviewer://${sourceRunId}`;
|
|
957
|
+
try {
|
|
958
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
959
|
+
}
|
|
960
|
+
catch (stateErr) {
|
|
961
|
+
this.emitRolloutReviewerEvent('rollout_reviewer_mark_succeeded_failed', taskId, {
|
|
962
|
+
taskId,
|
|
963
|
+
runId: sourceRunId,
|
|
964
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
965
|
+
});
|
|
966
|
+
throw stateErr;
|
|
967
|
+
}
|
|
968
|
+
this.phase = RunnerPhase.Completed;
|
|
969
|
+
return RolloutReviewerRunner.buildResumeResult({
|
|
970
|
+
taskId,
|
|
971
|
+
runId: sourceRunId,
|
|
972
|
+
artifactId: `pi-art-${taskId}-${sourceRunId}`,
|
|
973
|
+
resultRef,
|
|
974
|
+
output,
|
|
975
|
+
attemptCount: leasedTask.attemptCount,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* 从 runs 表恢复 intent 落库前已持久化的 validated output。
|
|
980
|
+
* intent 的存在保证 updateRunOutput 曾成功 (顺序: output → artifact →
|
|
981
|
+
* decision+intent);缺失/损坏 = authority 记录的存储腐坏 → fail loud
|
|
982
|
+
* (storage_unavailable → retryOrFail → max attempts → failed,人工介入)。
|
|
983
|
+
*/
|
|
984
|
+
async recoverIntentOutput(taskId, sourceRunId) {
|
|
985
|
+
const runs = await this.stateManager.getRunsByTask(taskId);
|
|
986
|
+
const run = runs.find((r) => r.runId === sourceRunId);
|
|
987
|
+
const raw = run?.outputPayload;
|
|
988
|
+
if (typeof raw !== 'string' || raw.length === 0) {
|
|
989
|
+
throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} has no outputPayload`);
|
|
990
|
+
}
|
|
991
|
+
let parsed;
|
|
992
|
+
try {
|
|
993
|
+
parsed = JSON.parse(raw);
|
|
994
|
+
}
|
|
995
|
+
catch {
|
|
996
|
+
throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} has unparseable outputPayload`);
|
|
997
|
+
}
|
|
998
|
+
// rc-1: 提取 echo 的 lineage 供验证器交叉核对 (持久化前已经过完整
|
|
999
|
+
// validate + lineage echo reconciliation,这里是对存储腐坏的防御)
|
|
1000
|
+
let echoedArtifactId;
|
|
1001
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
1002
|
+
const v = parsed.sourceEvaluatorArtifactId;
|
|
1003
|
+
if (typeof v === 'string')
|
|
1004
|
+
echoedArtifactId = v;
|
|
1005
|
+
}
|
|
1006
|
+
// runtime-contract-exempt: ERR-001 minimal shape check then full validator.validate — same pattern as fetchAndParseOutput; the cast only narrows post-validation
|
|
1007
|
+
const candidate = parsed;
|
|
1008
|
+
const vr = await this.validator.validate(candidate, taskId, echoedArtifactId);
|
|
1009
|
+
if (!vr.valid) {
|
|
1010
|
+
throw new PDRuntimeError('storage_unavailable', `completion intent output unrecoverable: run ${sourceRunId} of task ${taskId} failed revalidation (${vr.errors.join('; ')})`);
|
|
1011
|
+
}
|
|
1012
|
+
return candidate;
|
|
1013
|
+
}
|
|
1014
|
+
static buildResumeResult(r) {
|
|
1015
|
+
return {
|
|
1016
|
+
status: 'succeeded',
|
|
1017
|
+
taskId: r.taskId,
|
|
1018
|
+
runId: r.runId,
|
|
1019
|
+
artifactId: r.artifactId,
|
|
1020
|
+
resultRef: r.resultRef,
|
|
1021
|
+
contextHash: `resume-${r.runId}`,
|
|
1022
|
+
output: r.output,
|
|
1023
|
+
attemptCount: r.attemptCount,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* B: intent 标 APPLIED — transition 已 materialize 后的持久化确认。
|
|
1028
|
+
* 写失败 throw (fail loud): 重放会经 materialize 检查安全收敛。
|
|
1029
|
+
*/
|
|
1030
|
+
async markRevisionIntentAppliedOrThrow(taskId, iteration, payload) {
|
|
1031
|
+
await this.recordRolloutRevisionRouting(taskId, {
|
|
1032
|
+
requiredChanges: payload.requiredChanges,
|
|
1033
|
+
revisionIteration: iteration,
|
|
1034
|
+
sourceRolloutTaskId: payload.sourceRolloutTaskId,
|
|
1035
|
+
sourceArtifactId: payload.sourceArtifactId,
|
|
1036
|
+
targetTaskKind: payload.targetTaskKind,
|
|
1037
|
+
status: 'applied',
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
/** 记录修订路由 (budget 依据) */
|
|
1041
|
+
async recordRolloutRevisionRouting(taskId, payload) {
|
|
1042
|
+
try {
|
|
1043
|
+
const raw = await this.stateManager.getTask(taskId);
|
|
1044
|
+
if (!raw)
|
|
1045
|
+
return;
|
|
1046
|
+
const piTask = hydratePITaskRecord(raw);
|
|
1047
|
+
if (!piTask)
|
|
1048
|
+
return;
|
|
1049
|
+
const merged = mergePITaskMetadata(piTask, {
|
|
1050
|
+
rolloutRevisionPayload: payload,
|
|
1051
|
+
});
|
|
1052
|
+
await this.stateManager.updateTaskDiagnosticJson(taskId, createPITaskDiagnosticJson(merged));
|
|
1053
|
+
}
|
|
1054
|
+
catch (err) {
|
|
1055
|
+
// C (外部复核): routing intent 持久化失败必须 fail loud (caller 已把
|
|
1056
|
+
// record 前置于 reopen — 此 throw 阻止无 budget 记录的 reopen 发生)
|
|
1057
|
+
this.emitRolloutReviewerEvent('rollout_revision_record_failed', taskId, {
|
|
1058
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1059
|
+
nextAction: 'task_will_retry; record is idempotent overwrite',
|
|
1060
|
+
});
|
|
1061
|
+
throw err;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/** C: 显式 fail-loud 包装 — routing intent 是 transition 的组成部分。 */
|
|
1065
|
+
async recordRolloutRevisionRoutingOrThrow(taskId, payload) {
|
|
1066
|
+
await this.recordRolloutRevisionRouting(taskId, payload);
|
|
1067
|
+
}
|
|
397
1068
|
async handleRuntimeFailure(taskId, task, runStatus) {
|
|
398
1069
|
const errorCategory = this.mapRunStatusToErrorCategory(runStatus.status);
|
|
399
1070
|
this.emitRolloutReviewerEvent('rollout_reviewer_run_failed', taskId, {
|