@principles/pd-cli 1.132.0 → 1.134.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/commands/__tests__/intent-flag-wiring.test.js +74 -3
- package/dist/commands/__tests__/intent-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +2 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +60 -0
- package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +11 -4
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -1
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
- package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
- package/dist/commands/admission-gate.d.ts +24 -0
- package/dist/commands/admission-gate.d.ts.map +1 -0
- package/dist/commands/admission-gate.js +29 -0
- package/dist/commands/admission-gate.js.map +1 -0
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +72 -6
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +40 -0
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/demo-story-a.d.ts.map +1 -1
- package/dist/commands/demo-story-a.js +4 -2
- package/dist/commands/demo-story-a.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +155 -8
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/errors-list.d.ts +25 -0
- package/dist/commands/errors-list.d.ts.map +1 -0
- package/dist/commands/errors-list.js +368 -0
- package/dist/commands/errors-list.js.map +1 -0
- package/dist/commands/health-codex.d.ts +7 -0
- package/dist/commands/health-codex.d.ts.map +1 -0
- package/dist/commands/health-codex.js +209 -0
- package/dist/commands/health-codex.js.map +1 -0
- package/dist/commands/health.d.ts +24 -0
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +48 -0
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/intent.d.ts +21 -4
- package/dist/commands/intent.d.ts.map +1 -1
- package/dist/commands/intent.js +88 -27
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/pain-evidence.d.ts +6 -0
- package/dist/commands/pain-evidence.d.ts.map +1 -1
- package/dist/commands/pain-evidence.js +6 -0
- package/dist/commands/pain-evidence.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +21 -0
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/pain-retry.d.ts +4 -0
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +327 -32
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/proven-channel-baseline.d.ts.map +1 -1
- package/dist/commands/proven-channel-baseline.js +4 -2
- package/dist/commands/proven-channel-baseline.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts +30 -0
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +567 -11
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-canary.d.ts.map +1 -1
- package/dist/commands/runtime-canary.js +32 -7
- package/dist/commands/runtime-canary.js.map +1 -1
- package/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
- package/dist/commands/runtime-health-snapshot.js +4 -2
- package/dist/commands/runtime-health-snapshot.js.map +1 -1
- package/dist/commands/runtime-init.d.ts +43 -0
- package/dist/commands/runtime-init.d.ts.map +1 -0
- package/dist/commands/runtime-init.js +264 -0
- package/dist/commands/runtime-init.js.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts +25 -0
- package/dist/commands/runtime-internalization-context-trace.d.ts.map +1 -0
- package/dist/commands/runtime-internalization-context-trace.js +374 -0
- package/dist/commands/runtime-internalization-context-trace.js.map +1 -0
- package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-integrity.js +4 -2
- package/dist/commands/runtime-internalization-integrity.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +20 -5
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +149 -6
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/commands/runtime-uat.d.ts.map +1 -1
- package/dist/commands/runtime-uat.guard.test.js +13 -15
- package/dist/commands/runtime-uat.guard.test.js.map +1 -1
- package/dist/commands/runtime-uat.js +4 -3
- package/dist/commands/runtime-uat.js.map +1 -1
- package/dist/config-reader.js +1 -1
- package/dist/config-reader.js.map +1 -1
- package/dist/index.js +54 -78
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +2 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +1 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js +304 -0
- package/dist/services/__tests__/evaluator-runner-deps.test.js.map +1 -0
- package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
- package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
- package/dist/services/config-doctor.d.ts.map +1 -1
- package/dist/services/config-doctor.js +9 -2
- package/dist/services/config-doctor.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +9 -0
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/pd-config-loader.js +2 -2
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/proven-channel-baseline-runner.d.ts.map +1 -1
- package/dist/services/proven-channel-baseline-runner.js +10 -2
- package/dist/services/proven-channel-baseline-runner.js.map +1 -1
- package/dist/services/quality-scorecard/data-extractor.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts +77 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +111 -7
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/dist/services/rulehost-readiness.js.map +1 -1
- package/dist/services/runtime-adapter-resolver.d.ts +1 -0
- package/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/dist/services/runtime-adapter-resolver.js +3 -0
- package/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +11 -0
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/dist/utils/production-workspace-guard.d.ts +4 -4
- package/dist/utils/production-workspace-guard.d.ts.map +1 -1
- package/dist/utils/production-workspace-guard.js +19 -16
- package/dist/utils/production-workspace-guard.js.map +1 -1
- package/dist/utils/production-workspace-guard.test.js +54 -19
- package/dist/utils/production-workspace-guard.test.js.map +1 -1
- package/package.json +8 -5
- package/scripts/migrate-illegal-expected-decision.ts +252 -0
- package/src/commands/__tests__/intent-flag-wiring.test.ts +88 -3
- package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +73 -0
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +31 -24
- package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +12 -0
- package/src/commands/admission-gate.ts +30 -0
- package/src/commands/candidate.ts +74 -6
- package/src/commands/console.ts +40 -0
- package/src/commands/demo-story-a.ts +4 -2
- package/src/commands/diagnose.ts +158 -10
- package/src/commands/errors-list.ts +440 -0
- package/src/commands/health-codex.ts +249 -0
- package/src/commands/health.ts +60 -0
- package/src/commands/intent.ts +98 -29
- package/src/commands/pain-evidence.ts +6 -0
- package/src/commands/pain-record.ts +21 -0
- package/src/commands/pain-retry.ts +353 -35
- package/src/commands/proven-channel-baseline.ts +4 -2
- package/src/commands/runtime-activation.ts +657 -12
- package/src/commands/runtime-canary.ts +31 -5
- package/src/commands/runtime-health-snapshot.ts +4 -2
- package/src/commands/runtime-init.ts +305 -0
- package/src/commands/runtime-internalization-context-trace.ts +445 -0
- package/src/commands/runtime-internalization-enqueue-successors.ts +3 -1
- package/src/commands/runtime-internalization-integrity.ts +4 -2
- package/src/commands/runtime-internalization-run-once.ts +20 -5
- package/src/commands/runtime-internalization-run-rulehost.ts +159 -6
- package/src/commands/runtime-uat.guard.test.ts +14 -16
- package/src/commands/runtime-uat.ts +4 -3
- package/src/config-reader.ts +1 -1
- package/src/index.ts +69 -78
- package/src/services/__tests__/evaluator-runner-deps.test.ts +341 -0
- package/src/services/config-doctor.ts +9 -2
- package/src/services/pain-flood-simulation-runner.ts +9 -0
- package/src/services/pd-config-loader.ts +2 -2
- package/src/services/proven-channel-baseline-runner.ts +10 -3
- package/src/services/rulehost-pipeline-runner.ts +157 -6
- package/src/services/runtime-adapter-resolver.ts +4 -0
- package/src/services/synthetic-baseline-runner.ts +11 -0
- package/src/utils/production-workspace-guard.test.ts +62 -20
- package/src/utils/production-workspace-guard.ts +19 -16
- package/tests/bdd/cli-contract.steps.ts +343 -0
- package/tests/bdd/context-trace.steps.ts +229 -0
- package/tests/bdd/support/gherkin-loader.ts +147 -0
- package/tests/bdd/support/repo-root.ts +62 -0
- package/tests/bdd/support/vitest-bdd.ts +179 -0
- package/tests/commands/admission-gate-coverage.test.ts +79 -0
- package/tests/commands/candidate-audit-repair.test.ts +77 -0
- package/tests/commands/candidate-intake.test.ts +155 -10
- package/tests/commands/candidate-internalization-backfill.test.ts +30 -0
- package/tests/commands/cli-help-snapshot.test.ts +15 -1
- package/tests/commands/console-open.test.ts +19 -0
- package/tests/commands/diagnose.test.ts +619 -14
- package/tests/commands/errors-list.test.ts +678 -0
- package/tests/commands/health-codex.test.ts +243 -0
- package/tests/commands/health-host-wiring.test.ts +76 -0
- package/tests/commands/intent.test.ts +4 -4
- package/tests/commands/pain-record.test.ts +80 -0
- package/tests/commands/pain-retry.test.ts +94 -3
- package/tests/commands/run-rulehost-handler.test.ts +133 -0
- package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +227 -0
- package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +167 -0
- package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +262 -0
- package/tests/commands/runtime-activation-list-flag-wiring.test.ts +193 -0
- package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +222 -0
- package/tests/commands/runtime-activation.test.ts +1090 -5
- package/tests/commands/runtime-canary.test.ts +50 -0
- package/tests/commands/runtime-features.test.ts +2 -0
- package/tests/commands/runtime-init-empty-workspace.test.ts +234 -0
- package/tests/commands/runtime-init.test.ts +346 -0
- package/tests/commands/runtime-internalization-context-trace.test.ts +110 -0
- package/tests/commands/runtime-internalization.test.ts +2 -2
- package/tests/config-reader.test.ts +6 -5
- package/tests/e2e/cross-package-acceptance.test.ts +55 -13
- package/tests/services/mainline-snapshot-assembler.test.ts +6 -3
- package/tests/services/resolve-runtime-from-pd-config.test.ts +2 -0
- package/tests/services/rulehost-pipeline-runner.test.ts +1 -1
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-510 (DEFECT-004): EvaluatorRunner repair-loop CLI wiring (EP-02).
|
|
3
|
+
*
|
|
4
|
+
* Tests that the CLI production path constructs `EvaluatorRunner` with the
|
|
5
|
+
* repair-loop deps (`isRepairLoopEnabled` + `seedArtificerRepairTask`) so the
|
|
6
|
+
* PRI-509 core logic is actually invoked at runtime. Without this wiring, the
|
|
7
|
+
* repair loop is dead code in production — `evaluator needs_revision` seeds
|
|
8
|
+
* nothing (EP-02: component exists with isolated tests but real path never
|
|
9
|
+
* calls it).
|
|
10
|
+
*
|
|
11
|
+
* Trust boundary (rc-1, rc-2): the seeder writes `repairPayload` (sourced from
|
|
12
|
+
* evaluator LLM output) into `diagnosticJson` via the validated
|
|
13
|
+
* `createPITaskDiagnosticJson` serializer — no `as` bypass.
|
|
14
|
+
*
|
|
15
|
+
* Loop state freshness (rc-7, EP-05): the seeder does NOT infer
|
|
16
|
+
* `repairIteration` from any cached value; it uses the `repairPayload`
|
|
17
|
+
* constructed by the evaluator runner (which reads it from the dependency
|
|
18
|
+
* artificer task's metadata, written at task creation time).
|
|
19
|
+
*
|
|
20
|
+
* Fail loud (rc-9, EP-03): when `isRepairLoopEnabled` returns false, the
|
|
21
|
+
* `EvaluatorRunner` follows the legacy path (no seeder call). This is
|
|
22
|
+
* observable backward-compatible behavior, not a silent fallback.
|
|
23
|
+
*
|
|
24
|
+
* ERR refs:
|
|
25
|
+
* - EP-02 / ERR-011 / ERR-024: production path wiring — fix the wiring,
|
|
26
|
+
* not just the leaf helper.
|
|
27
|
+
* - EP-04 / cli-7-test-wiring: tests exercise the real helper used by both
|
|
28
|
+
* CLI construction sites (`runtime-internalization-run-once.ts`,
|
|
29
|
+
* `rulehost-pipeline-runner.ts`).
|
|
30
|
+
*/
|
|
31
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
32
|
+
import * as fs from 'node:fs';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import * as os from 'node:os';
|
|
35
|
+
import { createEvaluatorRunnerDeps } from '../rulehost-pipeline-runner.js';
|
|
36
|
+
import type { SeedArtificerRepairParams } from '@principles/core/runtime-v2';
|
|
37
|
+
import type { RuntimeStateManager, TaskRecord } from '@principles/core/runtime-v2';
|
|
38
|
+
|
|
39
|
+
// ─── Test workspace helpers ────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function createTempWorkspace(featureFlagEnabled: boolean | null): string {
|
|
42
|
+
// CodeQL: use mkdtempSync for atomic, unpredictable temp dir creation
|
|
43
|
+
// (avoids path-prediction risk from Date.now() + Math.random()).
|
|
44
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri-510-'));
|
|
45
|
+
const pdDir = path.join(tmpDir, '.pd');
|
|
46
|
+
fs.mkdirSync(pdDir, { recursive: true });
|
|
47
|
+
// When featureFlagEnabled is null, write a config WITHOUT the
|
|
48
|
+
// evaluator_artificer_repair_loop entry (defaults apply → false).
|
|
49
|
+
// Otherwise include the 3 core features + the repair-loop flag.
|
|
50
|
+
const repairFlagEntry =
|
|
51
|
+
featureFlagEnabled === null
|
|
52
|
+
? ''
|
|
53
|
+
: ` evaluator_artificer_repair_loop: { category: quiet, enabled: ${featureFlagEnabled}, since: '2026-07-04' }\n`;
|
|
54
|
+
const yaml = `
|
|
55
|
+
version: 1
|
|
56
|
+
features:
|
|
57
|
+
prompt: { category: core, enabled: true }
|
|
58
|
+
code_tool_hook: { category: core, enabled: true }
|
|
59
|
+
defer_archive: { category: core, enabled: true }
|
|
60
|
+
${repairFlagEntry}workspace:
|
|
61
|
+
default: ${tmpDir.replace(/\\/g, '/')}
|
|
62
|
+
internalAgents:
|
|
63
|
+
defaultRuntime: pi-ai.lmstudio
|
|
64
|
+
agents:
|
|
65
|
+
diagnostician:
|
|
66
|
+
enabled: true
|
|
67
|
+
dreamer:
|
|
68
|
+
enabled: true
|
|
69
|
+
philosopher:
|
|
70
|
+
enabled: true
|
|
71
|
+
scribe:
|
|
72
|
+
enabled: true
|
|
73
|
+
artificer:
|
|
74
|
+
enabled: true
|
|
75
|
+
runtimeProfiles:
|
|
76
|
+
pi-ai.lmstudio:
|
|
77
|
+
type: pi-ai
|
|
78
|
+
provider: lmstudio
|
|
79
|
+
model: qwen3.6-27b-mtp
|
|
80
|
+
apiKeyEnv: LMSTUDIO_API_KEY
|
|
81
|
+
baseUrl: http://localhost:1234/v1
|
|
82
|
+
`;
|
|
83
|
+
fs.writeFileSync(path.join(pdDir, 'config.yaml'), yaml, 'utf-8');
|
|
84
|
+
return tmpDir;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function cleanupWorkspace(dir: string): void {
|
|
88
|
+
try {
|
|
89
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
90
|
+
} catch {
|
|
91
|
+
// best effort
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Mock stateManager ─────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function createMockStateManager(): {
|
|
98
|
+
stateManager: RuntimeStateManager;
|
|
99
|
+
createdTasks: TaskRecord[];
|
|
100
|
+
} {
|
|
101
|
+
const createdTasks: TaskRecord[] = [];
|
|
102
|
+
const stateManager = {
|
|
103
|
+
createTask: vi.fn(async (record: Omit<TaskRecord, 'createdAt' | 'updatedAt'>) => {
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
const task: TaskRecord = { ...record, createdAt: now, updatedAt: now };
|
|
106
|
+
createdTasks.push(task);
|
|
107
|
+
return task;
|
|
108
|
+
}),
|
|
109
|
+
} as unknown as RuntimeStateManager;
|
|
110
|
+
return { stateManager, createdTasks };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Fixtures ───────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
function makeValidSeedParams(): SeedArtificerRepairParams {
|
|
116
|
+
return {
|
|
117
|
+
repairPayload: {
|
|
118
|
+
requiredChanges: ['fix path validation', 'add error handling'],
|
|
119
|
+
concerns: ['code quality is poor'],
|
|
120
|
+
previousScore: 0.55,
|
|
121
|
+
repairIteration: 1,
|
|
122
|
+
sourceArtificerArtifactId: 'pi-art-artificer-original',
|
|
123
|
+
sourceEvaluatorTaskId: 'evaluator-r0',
|
|
124
|
+
},
|
|
125
|
+
inheritedDependencyTaskIds: ['scribe-task-001'],
|
|
126
|
+
inheritedChannel: 'prompt',
|
|
127
|
+
inheritedTimeoutMs: 300_000,
|
|
128
|
+
inheritedInputArtifactRefs: [
|
|
129
|
+
{ artifactType: 'principle', ref: 'pi-art-scribe-001' },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
describe('PRI-510 (DEFECT-004): createEvaluatorRunnerDeps wires repair loop into CLI (EP-02)', () => {
|
|
137
|
+
let tmpWorkspaces: string[] = [];
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
tmpWorkspaces = [];
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(() => {
|
|
144
|
+
for (const dir of tmpWorkspaces) cleanupWorkspace(dir);
|
|
145
|
+
tmpWorkspaces = [];
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('flag enabled → isRepairLoopEnabled() returns true (EP-02: production wiring works)', () => {
|
|
149
|
+
const workspaceDir = createTempWorkspace(true);
|
|
150
|
+
tmpWorkspaces.push(workspaceDir);
|
|
151
|
+
const { stateManager } = createMockStateManager();
|
|
152
|
+
|
|
153
|
+
const deps = createEvaluatorRunnerDeps({
|
|
154
|
+
stateManager,
|
|
155
|
+
runtimeAdapter: {} as never,
|
|
156
|
+
eventEmitter: {} as never,
|
|
157
|
+
validator: {} as never,
|
|
158
|
+
artifactStore: {} as never,
|
|
159
|
+
workspaceDir,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(deps.isRepairLoopEnabled).toBeDefined();
|
|
163
|
+
expect(typeof deps.isRepairLoopEnabled).toBe('function');
|
|
164
|
+
// Guard before call — rc-2: no `!` non-null assertion (lint rule).
|
|
165
|
+
if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
|
|
166
|
+
expect(deps.isRepairLoopEnabled()).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('flag disabled → isRepairLoopEnabled() returns false (backward compatible)', () => {
|
|
170
|
+
const workspaceDir = createTempWorkspace(false);
|
|
171
|
+
tmpWorkspaces.push(workspaceDir);
|
|
172
|
+
const { stateManager } = createMockStateManager();
|
|
173
|
+
|
|
174
|
+
const deps = createEvaluatorRunnerDeps({
|
|
175
|
+
stateManager,
|
|
176
|
+
runtimeAdapter: {} as never,
|
|
177
|
+
eventEmitter: {} as never,
|
|
178
|
+
validator: {} as never,
|
|
179
|
+
artifactStore: {} as never,
|
|
180
|
+
workspaceDir,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(typeof deps.isRepairLoopEnabled).toBe('function');
|
|
184
|
+
if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
|
|
185
|
+
expect(deps.isRepairLoopEnabled()).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('flag absent in config → isRepairLoopEnabled() returns false (defaults apply)', () => {
|
|
189
|
+
const workspaceDir = createTempWorkspace(null);
|
|
190
|
+
tmpWorkspaces.push(workspaceDir);
|
|
191
|
+
const { stateManager } = createMockStateManager();
|
|
192
|
+
|
|
193
|
+
const deps = createEvaluatorRunnerDeps({
|
|
194
|
+
stateManager,
|
|
195
|
+
runtimeAdapter: {} as never,
|
|
196
|
+
eventEmitter: {} as never,
|
|
197
|
+
validator: {} as never,
|
|
198
|
+
artifactStore: {} as never,
|
|
199
|
+
workspaceDir,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(typeof deps.isRepairLoopEnabled).toBe('function');
|
|
203
|
+
if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
|
|
204
|
+
expect(deps.isRepairLoopEnabled()).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('malformed config → isRepairLoopEnabled() returns false (rc-9: fail safe, not throw)', () => {
|
|
208
|
+
// CodeQL: use mkdtempSync for atomic, unpredictable temp dir creation.
|
|
209
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pri-510-malformed-'));
|
|
210
|
+
tmpWorkspaces.push(tmpDir);
|
|
211
|
+
const pdDir = path.join(tmpDir, '.pd');
|
|
212
|
+
fs.mkdirSync(pdDir, { recursive: true });
|
|
213
|
+
fs.writeFileSync(path.join(pdDir, 'config.yaml'), 'this: is: not: valid: yaml: [', 'utf-8');
|
|
214
|
+
|
|
215
|
+
const { stateManager } = createMockStateManager();
|
|
216
|
+
|
|
217
|
+
const deps = createEvaluatorRunnerDeps({
|
|
218
|
+
stateManager,
|
|
219
|
+
runtimeAdapter: {} as never,
|
|
220
|
+
eventEmitter: {} as never,
|
|
221
|
+
validator: {} as never,
|
|
222
|
+
artifactStore: {} as never,
|
|
223
|
+
workspaceDir: tmpDir,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Malformed config must NOT throw — return false so the legacy path runs.
|
|
227
|
+
expect(typeof deps.isRepairLoopEnabled).toBe('function');
|
|
228
|
+
if (typeof deps.isRepairLoopEnabled !== 'function') throw new Error('isRepairLoopEnabled missing');
|
|
229
|
+
expect(deps.isRepairLoopEnabled()).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('seedArtificerRepairTask → creates artificer task with repairPayload in diagnosticJson (rc-1, rc-6)', async () => {
|
|
233
|
+
const workspaceDir = createTempWorkspace(true);
|
|
234
|
+
tmpWorkspaces.push(workspaceDir);
|
|
235
|
+
const { stateManager, createdTasks } = createMockStateManager();
|
|
236
|
+
const params = makeValidSeedParams();
|
|
237
|
+
|
|
238
|
+
const deps = createEvaluatorRunnerDeps({
|
|
239
|
+
stateManager,
|
|
240
|
+
runtimeAdapter: {} as never,
|
|
241
|
+
eventEmitter: {} as never,
|
|
242
|
+
validator: {} as never,
|
|
243
|
+
artifactStore: {} as never,
|
|
244
|
+
workspaceDir,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(deps.seedArtificerRepairTask).toBeDefined();
|
|
248
|
+
expect(typeof deps.seedArtificerRepairTask).toBe('function');
|
|
249
|
+
if (typeof deps.seedArtificerRepairTask !== 'function') throw new Error('seedArtificerRepairTask missing');
|
|
250
|
+
const repairTaskId = await deps.seedArtificerRepairTask(params);
|
|
251
|
+
|
|
252
|
+
// Returns a non-empty task ID
|
|
253
|
+
expect(typeof repairTaskId).toBe('string');
|
|
254
|
+
expect(repairTaskId.length).toBeGreaterThan(0);
|
|
255
|
+
|
|
256
|
+
// Exactly one task created
|
|
257
|
+
expect(createdTasks).toHaveLength(1);
|
|
258
|
+
const [task] = createdTasks;
|
|
259
|
+
if (!task) throw new Error('task not created');
|
|
260
|
+
|
|
261
|
+
// D1 (PRI-509): task kind is 'artificer' (reuses artificer-runner, not a
|
|
262
|
+
// new runner type)
|
|
263
|
+
expect(task.taskKind).toBe('artificer');
|
|
264
|
+
|
|
265
|
+
// Task starts pending so the orchestrator can pick it up
|
|
266
|
+
expect(task.status).toBe('pending');
|
|
267
|
+
|
|
268
|
+
// Task ID matches what the seeder returns
|
|
269
|
+
expect(task.taskId).toBe(repairTaskId);
|
|
270
|
+
|
|
271
|
+
// diagnosticJson must contain the repairPayload — round-trip via the
|
|
272
|
+
// parser to verify the validated shape (rc-1: treat parsed JSON as
|
|
273
|
+
// unknown; rc-2: no `as` bypass; rc-6: lineage fields consistent).
|
|
274
|
+
expect(task.diagnosticJson).toBeDefined();
|
|
275
|
+
expect(typeof task.diagnosticJson).toBe('string');
|
|
276
|
+
if (typeof task.diagnosticJson !== 'string') throw new Error('diagnosticJson not a string');
|
|
277
|
+
const parsed = JSON.parse(task.diagnosticJson);
|
|
278
|
+
const meta = parsed.pi_metadata;
|
|
279
|
+
expect(meta).toBeDefined();
|
|
280
|
+
expect(meta.repairPayload).toEqual(params.repairPayload);
|
|
281
|
+
|
|
282
|
+
// Inherited lineage must be preserved (PRI-508 context + PRI-509 deps)
|
|
283
|
+
expect(meta.dependencyTaskIds).toEqual(params.inheritedDependencyTaskIds);
|
|
284
|
+
expect(meta.channel).toBe(params.inheritedChannel);
|
|
285
|
+
expect(meta.timeoutMs).toBe(params.inheritedTimeoutMs);
|
|
286
|
+
expect(meta.inputArtifactRefs).toEqual(params.inheritedInputArtifactRefs);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('seedArtificerRepairTask → each call returns a UNIQUE task ID (rc-7: no stale state)', async () => {
|
|
290
|
+
const workspaceDir = createTempWorkspace(true);
|
|
291
|
+
tmpWorkspaces.push(workspaceDir);
|
|
292
|
+
const { stateManager } = createMockStateManager();
|
|
293
|
+
const params = makeValidSeedParams();
|
|
294
|
+
|
|
295
|
+
const deps = createEvaluatorRunnerDeps({
|
|
296
|
+
stateManager,
|
|
297
|
+
runtimeAdapter: {} as never,
|
|
298
|
+
eventEmitter: {} as never,
|
|
299
|
+
validator: {} as never,
|
|
300
|
+
artifactStore: {} as never,
|
|
301
|
+
workspaceDir,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (typeof deps.seedArtificerRepairTask !== 'function') throw new Error('seedArtificerRepairTask missing');
|
|
305
|
+
const id1 = await deps.seedArtificerRepairTask(params);
|
|
306
|
+
const id2 = await deps.seedArtificerRepairTask(params);
|
|
307
|
+
|
|
308
|
+
expect(id1).not.toBe(id2);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('deps spread contains all required base PeerRunnerDeps fields (EP-02: real path gets full deps)', () => {
|
|
312
|
+
const workspaceDir = createTempWorkspace(true);
|
|
313
|
+
tmpWorkspaces.push(workspaceDir);
|
|
314
|
+
const { stateManager } = createMockStateManager();
|
|
315
|
+
const fakeRuntimeAdapter = { kind: () => 'pi-ai' } as never;
|
|
316
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
317
|
+
const fakeEventEmitter = { emit: () => {} } as never;
|
|
318
|
+
const fakeValidator = { validate: () => ({ ok: true, value: {} }) } as never;
|
|
319
|
+
const fakeArtifactStore = { get: () => null } as never;
|
|
320
|
+
|
|
321
|
+
const deps = createEvaluatorRunnerDeps({
|
|
322
|
+
stateManager,
|
|
323
|
+
runtimeAdapter: fakeRuntimeAdapter,
|
|
324
|
+
eventEmitter: fakeEventEmitter,
|
|
325
|
+
validator: fakeValidator,
|
|
326
|
+
artifactStore: fakeArtifactStore,
|
|
327
|
+
workspaceDir,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Base PeerRunnerDeps preserved
|
|
331
|
+
expect(deps.stateManager).toBe(stateManager);
|
|
332
|
+
expect(deps.runtimeAdapter).toBe(fakeRuntimeAdapter);
|
|
333
|
+
expect(deps.eventEmitter).toBe(fakeEventEmitter);
|
|
334
|
+
expect(deps.validator).toBe(fakeValidator);
|
|
335
|
+
expect(deps.artifactStore).toBe(fakeArtifactStore);
|
|
336
|
+
|
|
337
|
+
// Repair-loop deps present
|
|
338
|
+
expect(deps.isRepairLoopEnabled).toBeTypeOf('function');
|
|
339
|
+
expect(deps.seedArtificerRepairTask).toBeTypeOf('function');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
@@ -517,12 +517,19 @@ export async function buildDoctorOutput(input: BuildDoctorInput): Promise<Doctor
|
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
// 5) Compute overall status
|
|
520
|
+
// Classification → status mapping:
|
|
521
|
+
// - config_missing (profile exists but fields empty/placeholder) → degraded
|
|
522
|
+
// This is the expected initial state for the default `pd.default` profile
|
|
523
|
+
// that ships with empty provider/model/apiKeyEnv. Users fill via console.
|
|
524
|
+
// - auth_missing (apiKeyEnv set but env var not in environment) → failed
|
|
525
|
+
// User configured the profile but the secret is missing at runtime.
|
|
526
|
+
// - needs_probe / rate_limit / unavailable → degraded
|
|
520
527
|
let status: DoctorStatus = 'ok';
|
|
521
528
|
const classifications = providerHealth.map((p) => p.classification);
|
|
522
|
-
if (classifications.includes('rate_limit') || classifications.includes('unavailable') || classifications.includes('needs_probe')) {
|
|
529
|
+
if (classifications.includes('rate_limit') || classifications.includes('unavailable') || classifications.includes('needs_probe') || classifications.includes('config_missing')) {
|
|
523
530
|
status = 'degraded';
|
|
524
531
|
}
|
|
525
|
-
if (classifications.includes('auth_missing')
|
|
532
|
+
if (classifications.includes('auth_missing')) {
|
|
526
533
|
status = 'failed';
|
|
527
534
|
}
|
|
528
535
|
if (!loadResult.ok) {
|
|
@@ -18,6 +18,7 @@ import { PainSignalBridge } from '@principles/core/runtime-v2';
|
|
|
18
18
|
import { CandidateIntakeService } from '@principles/core/runtime-v2';
|
|
19
19
|
import { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
|
|
20
20
|
import { makeDeterministicDiagnosticianOutput } from '@principles/core/runtime-v2';
|
|
21
|
+
import { contentHashFn } from './rulehost-pipeline-runner.js';
|
|
21
22
|
import type {
|
|
22
23
|
PainFloodSimulationSummary,
|
|
23
24
|
PainFloodStage,
|
|
@@ -83,6 +84,11 @@ async function runScenario(
|
|
|
83
84
|
painType: signal.painType,
|
|
84
85
|
source: 'pain-flood-simulation',
|
|
85
86
|
reason: signal.reason,
|
|
87
|
+
// Synthetic flood is a deterministic dedup/stress tool — it has no real
|
|
88
|
+
// host session. Provide synthetic evidence so the PEAT-B1 admission gate
|
|
89
|
+
// (default-on) does not short-circuit every signal before diagnosis,
|
|
90
|
+
// which would mask the dedup behaviour this tool exists to verify.
|
|
91
|
+
evidence: [{ sourceRef: `synthetic-flood:${signal.painId}`, note: 'Synthetic flood deterministic evidence anchor' }],
|
|
86
92
|
});
|
|
87
93
|
|
|
88
94
|
if (result.status === 'failed' || result.status === 'retried') {
|
|
@@ -246,6 +252,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
|
|
|
246
252
|
artifactStore: stateManager.piArtifactStore,
|
|
247
253
|
validator: new DefaultDiagRootCauseValidator(),
|
|
248
254
|
contextAssembler,
|
|
255
|
+
contentHashFn,
|
|
249
256
|
},
|
|
250
257
|
{
|
|
251
258
|
owner: 'pain-flood-simulation',
|
|
@@ -262,6 +269,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
|
|
|
262
269
|
eventEmitter,
|
|
263
270
|
artifactStore: stateManager.piArtifactStore,
|
|
264
271
|
validator: new DefaultDiagDistillerValidator(),
|
|
272
|
+
contentHashFn,
|
|
265
273
|
},
|
|
266
274
|
{
|
|
267
275
|
owner: 'pain-flood-simulation',
|
|
@@ -278,6 +286,7 @@ export async function runPainFloodSimulation(opts: PainFloodSimulationRunnerOpti
|
|
|
278
286
|
eventEmitter,
|
|
279
287
|
artifactStore: stateManager.piArtifactStore,
|
|
280
288
|
committer,
|
|
289
|
+
contentHashFn,
|
|
281
290
|
},
|
|
282
291
|
{
|
|
283
292
|
owner: 'pain-flood-simulation',
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import * as os from 'os';
|
|
17
|
-
import yaml from 'js-yaml';
|
|
17
|
+
import * as yaml from 'js-yaml';
|
|
18
18
|
import {
|
|
19
19
|
validatePdConfig,
|
|
20
20
|
computeEffectivePdConfig,
|
|
@@ -299,7 +299,7 @@ function loadOpenClawPluginConfig(): { workspace?: string } | null {
|
|
|
299
299
|
parsed !== null &&
|
|
300
300
|
typeof parsed === 'object' &&
|
|
301
301
|
!Array.isArray(parsed) &&
|
|
302
|
-
'workspace'
|
|
302
|
+
Object.hasOwn(parsed, 'workspace') &&
|
|
303
303
|
typeof (parsed as Record<string, unknown>).workspace === 'string' &&
|
|
304
304
|
((parsed as Record<string, unknown>).workspace as string).length > 0
|
|
305
305
|
) {
|
|
@@ -26,11 +26,18 @@ export interface ProvenChannelBaselineRunnerOptions {
|
|
|
26
26
|
|
|
27
27
|
function isProductionWorkspace(workspaceDir: string): boolean {
|
|
28
28
|
const normalized = path.resolve(workspaceDir).toLowerCase();
|
|
29
|
-
const productionPrefixes = [
|
|
30
|
-
path.resolve('D:\\.openclaw\\workspace').toLowerCase(),
|
|
31
|
-
path.resolve('C:\\Users\\Administrator\\.openclaw\\workspace').toLowerCase(),
|
|
29
|
+
const productionPrefixes: string[] = [
|
|
32
30
|
path.resolve(path.join(os.homedir(), '.openclaw', 'workspace')).toLowerCase(),
|
|
33
31
|
];
|
|
32
|
+
// Allow operator to declare additional production paths (cross-platform).
|
|
33
|
+
// Use path.delimiter (; on Windows, : on Unix) to separate multiple paths.
|
|
34
|
+
const extra = process.env.PD_PRODUCTION_WORKSPACE;
|
|
35
|
+
if (extra) {
|
|
36
|
+
for (const p of extra.split(path.delimiter)) {
|
|
37
|
+
const trimmed = p.trim();
|
|
38
|
+
if (trimmed) productionPrefixes.push(path.resolve(trimmed).toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
34
41
|
for (const prefix of productionPrefixes) {
|
|
35
42
|
if (normalized === prefix || normalized.startsWith(prefix + path.sep)) {
|
|
36
43
|
return true;
|
|
@@ -45,6 +45,11 @@ import {
|
|
|
45
45
|
DEFAULT_MAX_ROUNDS,
|
|
46
46
|
SqliteApprovalQueueStore,
|
|
47
47
|
getChannelRiskLevel,
|
|
48
|
+
// PRI-510: feature-flag resolvers imported from the core barrel (EP-02:
|
|
49
|
+
// pd-config-loader.ts only re-exports loadPdConfig + computeFlagsFromLoadResult;
|
|
50
|
+
// computeFeatureFlagsFromConfig / isFeatureEnabled live in core).
|
|
51
|
+
computeFeatureFlagsFromConfig,
|
|
52
|
+
isFeatureEnabled,
|
|
48
53
|
} from '@principles/core/runtime-v2';
|
|
49
54
|
import type {
|
|
50
55
|
AdversarialLoopResult,
|
|
@@ -53,10 +58,30 @@ import type {
|
|
|
53
58
|
RefinerRuleHostGateDeps,
|
|
54
59
|
PIArtifactStore,
|
|
55
60
|
ApprovalRecord,
|
|
61
|
+
BehaviorExamplePack,
|
|
62
|
+
// PRI-510: type-only import for the repair-loop deps contract (EP-02 wiring).
|
|
63
|
+
// RuntimeStateManager / StoreEventEmitter are already imported as values
|
|
64
|
+
// above; EvaluatorValidator is type-only.
|
|
65
|
+
EvaluatorRunnerDeps,
|
|
66
|
+
SeedArtificerRepairParams,
|
|
67
|
+
EvaluatorValidator,
|
|
56
68
|
} from '@principles/core/runtime-v2';
|
|
69
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
70
|
+
import { loadPdConfig } from './pd-config-loader.js';
|
|
57
71
|
/* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after main, matching codebase convention */
|
|
58
72
|
import { compileDemoRule } from './demo-rule-compiler.js';
|
|
59
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Layer 0 content-hash function for the internalization progressive
|
|
76
|
+
* disclosure (design §6.1). Core never imports `node:crypto`, so the plugin/CLI
|
|
77
|
+
* layer injects the algorithm. Consumed only when the
|
|
78
|
+
* `artifact_summary_redundancy` flag is on; harmless otherwise.
|
|
79
|
+
*
|
|
80
|
+
* Exported so other CLI command modules (e.g. `runtime-internalization-run-once`)
|
|
81
|
+
* can reuse the same algorithm without each re-importing `node:crypto`.
|
|
82
|
+
*/
|
|
83
|
+
export const contentHashFn = (input: string): string => createHash('sha256').update(input).digest('hex');
|
|
84
|
+
|
|
60
85
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
61
86
|
|
|
62
87
|
/**
|
|
@@ -113,6 +138,10 @@ export interface RuleHostPipelineOptions {
|
|
|
113
138
|
* capability is treated as OFF with reason 'code_rule_capability not provided'.
|
|
114
139
|
*/
|
|
115
140
|
readonly codeRuleCapability?: CodeRuleCapability;
|
|
141
|
+
/** Explicit Artificer contract selected by the workspace feature flag. */
|
|
142
|
+
readonly contextMode?: 'v1' | 'v2';
|
|
143
|
+
/** Required in v2 mode; assembled from Owner-labelled production evidence. */
|
|
144
|
+
readonly behaviorExamplePack?: BehaviorExamplePack;
|
|
116
145
|
/** Internalization channel for created tasks (default 'code_tool_hook'). */
|
|
117
146
|
readonly channel?: 'prompt' | 'code_tool_hook' | 'defer_archive';
|
|
118
147
|
/** Max adversarial rounds (PRD cap = 2). */
|
|
@@ -286,7 +315,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
286
315
|
stages.push({ name: 'dreamer', taskId: dreamerSeedTaskId, status: 'succeeded' });
|
|
287
316
|
} else {
|
|
288
317
|
const dreamerRunner = new DreamerRunner(
|
|
289
|
-
{ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore },
|
|
318
|
+
{ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore, contentHashFn },
|
|
290
319
|
runnerOptsFor(agentAdapters.dreamer),
|
|
291
320
|
);
|
|
292
321
|
const dreamerResult = await runStage(dreamerRunner, dreamerSeedTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -303,7 +332,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
303
332
|
const philosopherTaskId = `${correlation}-philosopher-${Date.now().toString(36)}`;
|
|
304
333
|
await createInternalizationTask(stateManager, philosopherTaskId, 'philosopher', [dreamerSeedTaskId], channel, timeoutMs);
|
|
305
334
|
const philosopherRunner = new PhilosopherRunner(
|
|
306
|
-
{ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore },
|
|
335
|
+
{ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore, contentHashFn },
|
|
307
336
|
runnerOptsFor(agentAdapters.philosopher),
|
|
308
337
|
);
|
|
309
338
|
const philosopherResult = await runStage(philosopherRunner, philosopherTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -319,7 +348,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
319
348
|
const scribeTaskId = `${correlation}-scribe-${Date.now().toString(36)}`;
|
|
320
349
|
await createInternalizationTask(stateManager, scribeTaskId, 'scribe', [philosopherTaskId], channel, timeoutMs);
|
|
321
350
|
const scribeRunner = new ScribeRunner(
|
|
322
|
-
{ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore },
|
|
351
|
+
{ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore, contentHashFn },
|
|
323
352
|
runnerOptsFor(agentAdapters.scribe),
|
|
324
353
|
);
|
|
325
354
|
const scribeResult = await runStage(scribeRunner, scribeTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -352,11 +381,25 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
352
381
|
}
|
|
353
382
|
onProgress('adversarial_loop', 'start');
|
|
354
383
|
const artificerRunner = new ArtificerRunner(
|
|
355
|
-
{
|
|
384
|
+
{
|
|
385
|
+
stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
|
|
386
|
+
contextMode: opts.contextMode ?? 'v1', behaviorExamplePack: opts.behaviorExamplePack, contentHashFn,
|
|
387
|
+
},
|
|
356
388
|
runnerOptsFor(capability.artificerAdapter),
|
|
357
389
|
);
|
|
390
|
+
// PRI-510 (DEFECT-004): construct EvaluatorRunnerDeps via the centralized
|
|
391
|
+
// helper so the repair-loop wiring (isRepairLoopEnabled + seeder) is
|
|
392
|
+
// actually invoked in the production CLI path. EP-02: prior code passed
|
|
393
|
+
// only the 5 base deps, leaving the repair loop as dead code at runtime.
|
|
358
394
|
const evaluatorRunner = new EvaluatorRunner(
|
|
359
|
-
{
|
|
395
|
+
createEvaluatorRunnerDeps({
|
|
396
|
+
stateManager,
|
|
397
|
+
runtimeAdapter: agentAdapters.evaluator,
|
|
398
|
+
eventEmitter,
|
|
399
|
+
validator: new DefaultEvaluatorValidator(),
|
|
400
|
+
artifactStore,
|
|
401
|
+
workspaceDir: opts.workspaceDir,
|
|
402
|
+
}),
|
|
360
403
|
{ ...runnerOptsFor(agentAdapters.evaluator), gateDeps: createSandboxGateDeps() },
|
|
361
404
|
);
|
|
362
405
|
|
|
@@ -405,7 +448,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
405
448
|
// Enqueue failed — the candidate artifact exists but is not in the
|
|
406
449
|
// approval queue. Degrade gracefully with a structured reason (ERR-002).
|
|
407
450
|
const enqueueErr = err instanceof Error ? err.message : String(err);
|
|
408
|
-
const degradeReason = `candidate_approved_but_enqueue_failed: ${enqueueErr}. Manual enqueue required: pd
|
|
451
|
+
const degradeReason = `candidate_approved_but_enqueue_failed: ${enqueueErr}. Manual enqueue required: pd activation dispatch --artifact-id ${loopResult.ruleArtifactId} --channel ${channel}`;
|
|
409
452
|
return {
|
|
410
453
|
decision: pipelineDecision,
|
|
411
454
|
painId: opts.painId,
|
|
@@ -528,6 +571,114 @@ async function createInternalizationTask(
|
|
|
528
571
|
});
|
|
529
572
|
}
|
|
530
573
|
|
|
574
|
+
// ── PRI-510 (DEFECT-004): EvaluatorRunner repair-loop CLI wiring ────────────
|
|
575
|
+
//
|
|
576
|
+
// EP-02 (Production Path Wiring): PRI-509 added the `isRepairLoopEnabled` and
|
|
577
|
+
// `seedArtificerRepairTask` deps to `EvaluatorRunnerDeps` (core), but the two
|
|
578
|
+
// CLI production paths (runtime-internalization-run-once.ts and this file)
|
|
579
|
+
// constructed `EvaluatorRunner` with only the base deps — the repair loop was
|
|
580
|
+
// dead code at runtime (evaluator needs_revision seeded nothing).
|
|
581
|
+
//
|
|
582
|
+
// This helper centralizes the construction of the full deps so both CLI sites
|
|
583
|
+
// stay in sync. The flag resolver reads `.pd/config.yaml` (the unified config
|
|
584
|
+
// — ADR-0016); the seeder writes the repair task via `stateManager.createTask`
|
|
585
|
+
// (the only sanctioned task-creation path; core peer runners are forbidden
|
|
586
|
+
// from calling it directly — architecture-regression.test.ts enforces this).
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Inputs for {@link createEvaluatorRunnerDeps}. Carries the base `PeerRunnerDeps`
|
|
590
|
+
* fields plus `workspaceDir` (used to resolve the feature flag from
|
|
591
|
+
* `.pd/config.yaml`).
|
|
592
|
+
*/
|
|
593
|
+
export interface CreateEvaluatorRunnerDepsInputs {
|
|
594
|
+
readonly stateManager: RuntimeStateManager;
|
|
595
|
+
readonly runtimeAdapter: PDRuntimeAdapter;
|
|
596
|
+
readonly eventEmitter: StoreEventEmitter;
|
|
597
|
+
readonly validator: EvaluatorValidator;
|
|
598
|
+
readonly artifactStore: PIArtifactStore;
|
|
599
|
+
/** Workspace directory containing `.pd/config.yaml` (flag source). */
|
|
600
|
+
readonly workspaceDir: string;
|
|
601
|
+
/**
|
|
602
|
+
* Layer 0 content-hash function (design §6.1). Optional — defaults to the
|
|
603
|
+
* module-level sha256-hex `contentHashFn`. Allow override for tests.
|
|
604
|
+
*/
|
|
605
|
+
readonly contentHashFn?: (input: string) => string;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Build the full `EvaluatorRunnerDeps` for CLI production paths, including the
|
|
610
|
+
* PRI-509 repair-loop wiring (EP-02: production path must invoke core logic).
|
|
611
|
+
*
|
|
612
|
+
* - `isRepairLoopEnabled`: reads `.pd/config.yaml` and resolves the
|
|
613
|
+
* `evaluator_artificer_repair_loop` flag. Defaults to false when the config
|
|
614
|
+
* is missing or malformed (rc-9: fail safe — legacy path runs, never throws).
|
|
615
|
+
* - `seedArtificerRepairTask`: creates a new `artificer` task carrying the
|
|
616
|
+
* `repairPayload` in `diagnosticJson` (rc-1, rc-6: serialized via the
|
|
617
|
+
* validated `createPITaskDiagnosticJson`, no `as` bypass). Reuses the
|
|
618
|
+
* artificer runner (PRI-509 D1) rather than introducing a new task kind.
|
|
619
|
+
*
|
|
620
|
+
* Returns the newly created repair task's ID (UUIDv4, rc-7: fresh per call).
|
|
621
|
+
*/
|
|
622
|
+
export function createEvaluatorRunnerDeps(inputs: CreateEvaluatorRunnerDepsInputs): EvaluatorRunnerDeps {
|
|
623
|
+
const { stateManager, runtimeAdapter, eventEmitter, validator, artifactStore, workspaceDir } = inputs;
|
|
624
|
+
return {
|
|
625
|
+
stateManager,
|
|
626
|
+
runtimeAdapter,
|
|
627
|
+
eventEmitter,
|
|
628
|
+
validator,
|
|
629
|
+
artifactStore,
|
|
630
|
+
// Layer 0 (design §6.1): inject the content-hash function so the evaluator
|
|
631
|
+
// writer can attach a `predecessorSummary.contentHash` for staleness
|
|
632
|
+
// detection. Defaults to the module-level sha256-hex constant.
|
|
633
|
+
contentHashFn: inputs.contentHashFn ?? contentHashFn,
|
|
634
|
+
isRepairLoopEnabled: (): boolean => {
|
|
635
|
+
// rc-9: never throw on malformed config — fail safe to false so the
|
|
636
|
+
// legacy (non-repair) path runs. The malformed config is already
|
|
637
|
+
// surfaced by `pd config doctor` and CLI start-up warnings.
|
|
638
|
+
try {
|
|
639
|
+
const result = loadPdConfig(workspaceDir);
|
|
640
|
+
const effective = result.ok ? result.effective : result.defaults;
|
|
641
|
+
const flags = computeFeatureFlagsFromConfig(effective);
|
|
642
|
+
return isFeatureEnabled(flags, 'evaluator_artificer_repair_loop');
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
seedArtificerRepairTask: async (params: SeedArtificerRepairParams): Promise<string> => {
|
|
648
|
+
// rc-7: each call gets a fresh task ID — never reuse a cached ID.
|
|
649
|
+
const repairTaskId = `artificer-repair-${randomUUID()}`;
|
|
650
|
+
await stateManager.createTask({
|
|
651
|
+
taskId: repairTaskId,
|
|
652
|
+
// D1 (PRI-509): task kind is 'artificer' — reuses the artificer
|
|
653
|
+
// runner, which detects repairPayload in diagnosticJson and
|
|
654
|
+
// forwards it to the prompt builder as repairFeedback.
|
|
655
|
+
taskKind: 'artificer',
|
|
656
|
+
status: 'pending',
|
|
657
|
+
attemptCount: 0,
|
|
658
|
+
maxAttempts: 3,
|
|
659
|
+
// rc-1, rc-6: serialize via the validated helper — repairPayload
|
|
660
|
+
// is untrusted LLM output but `createPITaskDiagnosticJson` writes
|
|
661
|
+
// it through `serializePITaskMetadata`, which `parsePITaskMetadata`
|
|
662
|
+
// re-validates on read (defense in depth).
|
|
663
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
664
|
+
// PITaskMetadata fields are mutable arrays (the metadata envelope
|
|
665
|
+
// serializes them as JSON arrays). The SeedArtificerRepairParams
|
|
666
|
+
// contract carries readonly arrays (rc-1: untrusted input from the
|
|
667
|
+
// evaluator LLM). Spread to a fresh mutable array — no mutation of
|
|
668
|
+
// the caller's data.
|
|
669
|
+
dependencyTaskIds: [...params.inheritedDependencyTaskIds],
|
|
670
|
+
channel: params.inheritedChannel,
|
|
671
|
+
timeoutMs: params.inheritedTimeoutMs,
|
|
672
|
+
inputArtifactRefs: [...params.inheritedInputArtifactRefs],
|
|
673
|
+
outputArtifactRefs: [],
|
|
674
|
+
repairPayload: params.repairPayload,
|
|
675
|
+
}),
|
|
676
|
+
});
|
|
677
|
+
return repairTaskId;
|
|
678
|
+
},
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
531
682
|
/**
|
|
532
683
|
* Run a single pipeline stage with bounded retry for transient `retried` status.
|
|
533
684
|
*
|