@principles/core 1.275.2 → 1.277.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/index.d.ts +2 -2
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +1 -1
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/context-resolution.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/context-resolution.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/context-resolution.test.js +377 -0
- package/dist/runtime-v2/internalization/__tests__/context-resolution.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js +8 -8
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.js +628 -0
- package/dist/runtime-v2/internalization/__tests__/shared-information-plane-runner.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts +23 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +104 -13
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/context-manifests.d.ts +90 -17
- package/dist/runtime-v2/internalization/context-manifests.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/context-manifests.js +124 -27
- package/dist/runtime-v2/internalization/context-manifests.js.map +1 -1
- package/dist/runtime-v2/internalization/context-resolution.d.ts +189 -0
- package/dist/runtime-v2/internalization/context-resolution.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/context-resolution.js +317 -0
- package/dist/runtime-v2/internalization/context-resolution.js.map +1 -0
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts +32 -0
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +72 -7
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +62 -17
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +116 -10
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/types/event-types.d.ts +27 -3
- package/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/dist/runtime-v2/types/event-types.js +6 -0
- package/dist/runtime-v2/types/event-types.js.map +1 -1
- package/dist/runtime-v2/types/index.d.ts +2 -2
- package/dist/runtime-v2/types/index.d.ts.map +1 -1
- package/dist/runtime-v2/types/index.js +1 -1
- package/dist/runtime-v2/types/index.js.map +1 -1
- package/dist/telemetry-event.d.ts +2 -2
- package/dist/telemetry-event.d.ts.map +1 -1
- package/dist/telemetry-event.js +47 -0
- package/dist/telemetry-event.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR B — Shared Information Plane runner-level regression (SPEC §38/§40).
|
|
3
|
+
*
|
|
4
|
+
* Proves through REAL runners + REAL CandidateLineage (durable artifact rows in
|
|
5
|
+
* an isolated temp SQLite — never a hand-stuffed available Map):
|
|
6
|
+
*
|
|
7
|
+
* T-A Normal Artificer, context_manifest_budget ON:
|
|
8
|
+
* ARTIFICER_MANIFEST tier2 (`dreamer.raw.candidates.0.*`) resolves from
|
|
9
|
+
* the durable Dreamer artifact through CandidateLineage ancestry, and
|
|
10
|
+
* the focused prompt does NOT leak repair/replay namespaces.
|
|
11
|
+
*
|
|
12
|
+
* T-B Evaluator Stage 2 (progressive_evaluator + context_manifest_budget ON):
|
|
13
|
+
* Stage 2's REQUIRED tier2 (`diagnostician.raw.evidence`,
|
|
14
|
+
* `dreamer.raw.candidates`) resolve from the durable ancestry chain —
|
|
15
|
+
* not silently absent (information floor §34/§35).
|
|
16
|
+
*
|
|
17
|
+
* T-C Artificer REPAIR round with the same flags ON:
|
|
18
|
+
* the replay evidence reaches the repair prompt through the
|
|
19
|
+
* ARTIFICER_REPAIR_MANIFEST RELATED channel (PR-A resolver reused, ≤16
|
|
20
|
+
* bound inherited) instead of the PR-A string channel, and the loop
|
|
21
|
+
* still closes FAIL → Repair → PASS → pi-rule.
|
|
22
|
+
*
|
|
23
|
+
* Flags-OFF behaviour is pinned by evaluator-artificer-repair-replay.test.ts
|
|
24
|
+
* (PR A) which must keep passing unchanged (T-D).
|
|
25
|
+
*/
|
|
26
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
import * as os from 'node:os';
|
|
30
|
+
import { RuntimeStateManager } from '../../store/runtime-state-manager.js';
|
|
31
|
+
import { EvaluatorRunner } from '../evaluator-runner.js';
|
|
32
|
+
import { ArtificerRunner } from '../artificer-runner.js';
|
|
33
|
+
import { DefaultEvaluatorValidator } from '../evaluator-output.js';
|
|
34
|
+
import { DefaultArtificerValidator } from '../artificer-output.js';
|
|
35
|
+
import { StoreEventEmitter } from '../../store/event-emitter.js';
|
|
36
|
+
import { SqliteConnection } from '../../store/sqlite-connection.js';
|
|
37
|
+
import { SqlitePIArtifactStore } from '../../store/artifact/sqlite-pi-artifact-store.js';
|
|
38
|
+
import { createPITaskDiagnosticJson } from '../pitask-metadata.js';
|
|
39
|
+
import { createProductionGateDeps } from '../../activation/production-gate-deps.js';
|
|
40
|
+
let workspaceDir;
|
|
41
|
+
let stateManager;
|
|
42
|
+
let emitter;
|
|
43
|
+
let emitted;
|
|
44
|
+
let store;
|
|
45
|
+
const DIAG_ID = 'diag-sip';
|
|
46
|
+
const DREAM_ID = 'dreamer-sip';
|
|
47
|
+
const PHIL_ID = 'philosopher-sip';
|
|
48
|
+
const SCRIBE_ID = 'scribe-sip';
|
|
49
|
+
const ART1_ID = 'artificer-sip1';
|
|
50
|
+
const EVAL1_ID = 'evaluator-sip1';
|
|
51
|
+
const REPAIR_ID = 'artificer-repair-sip';
|
|
52
|
+
const EVAL2_ID = 'evaluator-sip2';
|
|
53
|
+
const DIAG_ART = 'pi-art-diag-sip';
|
|
54
|
+
const DREAM_ART = 'pi-art-dream-sip';
|
|
55
|
+
const PHIL_ART = 'pi-art-phil-sip';
|
|
56
|
+
const SCRIBE_ART = 'pi-art-scribe-sip';
|
|
57
|
+
const ART1_ART = 'pi-art-art1-sip';
|
|
58
|
+
/** Marker resolvable ONLY through tier2 ancestry raw reads (T-A/T-B). */
|
|
59
|
+
const BETTER_DECISION_MARKER = 'ALWAYS-READ-BEFORE-WRITE-SIP-MARKER';
|
|
60
|
+
const PAIN_EVIDENCE_MARKER = 'PAIN-EVIDENCE-WITHOUT-READ-SIP-MARKER';
|
|
61
|
+
/** Field present ONLY in the full scribe contentJson — focused mode must not leak it. */
|
|
62
|
+
const SCRIBE_RAW_ONLY_MARKER = 'SCRIBE-RAW-ONLY-SIP-MARKER';
|
|
63
|
+
/** Round-2 RuleCode (identical to the PR-A regression fixture): risk-path
|
|
64
|
+
* dominance + read-before-write via context facts. The adversarial generator
|
|
65
|
+
* varies `context.facts`, so the second branch is what makes the real
|
|
66
|
+
* deterministic replay pass. */
|
|
67
|
+
const GOOD_RULE_CODE = `function evaluate(input, helpers) {
|
|
68
|
+
var rawPath = input.action.paramsSummary.path;
|
|
69
|
+
var p = (typeof rawPath === 'string') ? rawPath : (input.action.normalizedPath || '');
|
|
70
|
+
if (p.indexOf('/etc/') === 0 || p === '/etc/passwd') {
|
|
71
|
+
return { decision: 'block', matched: true, reason: 'risk path dominates prior context' };
|
|
72
|
+
}
|
|
73
|
+
var ctx = input.context;
|
|
74
|
+
if (ctx && ctx.facts && ctx.facts.priorReadOfTarget === 'no') {
|
|
75
|
+
return { decision: 'block', matched: true, reason: 'write target was not read first' };
|
|
76
|
+
}
|
|
77
|
+
return { decision: 'allow', matched: false, reason: 'no risk signal' };
|
|
78
|
+
}`;
|
|
79
|
+
const BAD_RULE_CODE = `function evaluate(input, helpers) {
|
|
80
|
+
if (input.action.paramsSummary.includes('/etc/passwd')) {
|
|
81
|
+
return { decision: 'block', matched: true, reason: 'risk path' };
|
|
82
|
+
}
|
|
83
|
+
return { decision: 'allow', matched: false, reason: 'safe path' };
|
|
84
|
+
}`;
|
|
85
|
+
const GOLDEN_TRACE_CASES = [
|
|
86
|
+
{ caseId: 'neg-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' },
|
|
87
|
+
{ caseId: 'pos-1', kind: 'positive', toolName: 'write_file', params: { path: '/workspace/src/a.ts' }, expectedDecision: 'allow' },
|
|
88
|
+
];
|
|
89
|
+
function flagsConfig(allOn) {
|
|
90
|
+
return {
|
|
91
|
+
config: {
|
|
92
|
+
version: 1,
|
|
93
|
+
features: {
|
|
94
|
+
artifact_summary_redundancy: { category: 'quiet', enabled: allOn },
|
|
95
|
+
context_manifest_budget: { category: 'quiet', enabled: allOn },
|
|
96
|
+
progressive_evaluator: { category: 'quiet', enabled: allOn },
|
|
97
|
+
},
|
|
98
|
+
runtimeProfiles: {},
|
|
99
|
+
internalAgents: {},
|
|
100
|
+
ui: { diagnostics: { mode: 'simple' } },
|
|
101
|
+
},
|
|
102
|
+
source: 'user_config',
|
|
103
|
+
warnings: [],
|
|
104
|
+
resolvedContextInjection: {
|
|
105
|
+
thinkingOs: false,
|
|
106
|
+
projectFocus: 'off',
|
|
107
|
+
evolutionContext: { enabled: true, maxMessages: 4, maxCharsPerMessage: 200 },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const FLAGS_ON = flagsConfig(true);
|
|
112
|
+
/** Layer 0 envelope matching the ArtifactSummary shape written under
|
|
113
|
+
* artifact_summary_redundancy (artifact-summary.ts). */
|
|
114
|
+
function summaryEnvelope(runnerKind, headline, fields) {
|
|
115
|
+
return {
|
|
116
|
+
schemaVersion: 1,
|
|
117
|
+
runnerKind,
|
|
118
|
+
headline,
|
|
119
|
+
fields,
|
|
120
|
+
derivedFrom: 'structured_output',
|
|
121
|
+
omittedFields: [],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- compact regression-fixture DSL: positional (id, kind, content) reads better at 20 call sites than an options object; same rule tolerance as attach-summary-envelope.ts
|
|
125
|
+
async function upsertArtifact(artifactId, sourceTaskId, lineageArtifactIds, content) {
|
|
126
|
+
const now = new Date().toISOString();
|
|
127
|
+
await store.upsertArtifact({
|
|
128
|
+
artifactId, artifactKind: 'principle', sourceTaskId,
|
|
129
|
+
lineageArtifactIds, validationStatus: 'validated',
|
|
130
|
+
contentJson: JSON.stringify(content),
|
|
131
|
+
createdAt: now, updatedAt: now,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Durable ancestry chain: diagnostician → dreamer → philosopher → scribe.
|
|
136
|
+
*
|
|
137
|
+
* The final diagnostic node defaults to the REAL split-pipeline identity:
|
|
138
|
+
* taskKind `diag_router` (SplitDiagnosticianRunner stage C — the task that
|
|
139
|
+
* owns the durable DiagnosticianOutputV1 artifact on the default chain). The
|
|
140
|
+
* manifest namespace `diagnostician` resolves to it via SEMANTIC_STAGE_ALIASES.
|
|
141
|
+
* Pass `'diagnostician'` to model the legacy monolithic identity (same output
|
|
142
|
+
* contract, pre-PRI-373 workspaces).
|
|
143
|
+
*/
|
|
144
|
+
async function seedInternalizationChain(diagKind = 'diag_router') {
|
|
145
|
+
const mk = async (id, kind, deps) => {
|
|
146
|
+
await stateManager.createTask({
|
|
147
|
+
taskId: id, taskKind: kind, status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
148
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
149
|
+
dependencyTaskIds: deps, channel: 'prompt', timeoutMs: 300_000,
|
|
150
|
+
inputArtifactRefs: [], outputArtifactRefs: [],
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
await stateManager.acquireLease({ taskId: id, owner: 'sip-test', runtimeKind: 'test-double' });
|
|
154
|
+
await stateManager.markTaskSucceeded(id);
|
|
155
|
+
};
|
|
156
|
+
await mk(DIAG_ID, diagKind, []);
|
|
157
|
+
await upsertArtifact(DIAG_ART, DIAG_ID, [], {
|
|
158
|
+
valid: true, diagnosisId: 'diag-sip-1',
|
|
159
|
+
summary: 'Agent wrote a controlled file without reading it first.',
|
|
160
|
+
rootCause: 'Tooling: write path skipped the read-before-write check.',
|
|
161
|
+
violatedPrinciples: [], evidence: [{ ref: 'pain-1', summary: PAIN_EVIDENCE_MARKER }],
|
|
162
|
+
recommendations: [{ kind: 'internalize', description: 'read before write' }],
|
|
163
|
+
confidence: 0.9,
|
|
164
|
+
// NOTE: no Layer 0 `summary` envelope on this node. DiagnosticianOutputV1
|
|
165
|
+
// already owns a top-level `summary` STRING, and `attachSummaryEnvelope`
|
|
166
|
+
// refuses to overwrite an existing `summary` key (base-peer-runner
|
|
167
|
+
// `artifact_summary_skipped`) — so `diagnostician.summary.*` is expected
|
|
168
|
+
// absent in production too. It stays declared (the Evaluator needs the
|
|
169
|
+
// pain context) and is carried by the information floor, not hidden.
|
|
170
|
+
});
|
|
171
|
+
await mk(DREAM_ID, 'dreamer', [DIAG_ID]);
|
|
172
|
+
await upsertArtifact(DREAM_ART, DREAM_ID, [DIAG_ART], {
|
|
173
|
+
valid: true, taskId: DREAM_ID, contextRefs: [], generatedAt: new Date().toISOString(),
|
|
174
|
+
candidates: [
|
|
175
|
+
{
|
|
176
|
+
candidateIndex: 0,
|
|
177
|
+
badDecision: 'wrote the file without reading it',
|
|
178
|
+
betterDecision: BETTER_DECISION_MARKER,
|
|
179
|
+
rationale: 'reading first keeps edits consistent with on-disk state',
|
|
180
|
+
confidence: 0.85,
|
|
181
|
+
riskLevel: 'low',
|
|
182
|
+
strategicPerspective: 'safety',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
candidateIndex: 1,
|
|
186
|
+
badDecision: 'ignored the diff',
|
|
187
|
+
betterDecision: 'compare before write',
|
|
188
|
+
rationale: 'diff-aware writes reduce churn',
|
|
189
|
+
confidence: 0.6,
|
|
190
|
+
riskLevel: 'medium',
|
|
191
|
+
strategicPerspective: 'correctness',
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
// Layer 0 envelope (artifact_summary_redundancy ON). The Evaluator's
|
|
195
|
+
// `dreamer.summary.*` resolves from THIS node via the ancestry channel —
|
|
196
|
+
// its direct predecessor (the artificer) cannot answer it.
|
|
197
|
+
summary: summaryEnvelope('dreamer', BETTER_DECISION_MARKER, {
|
|
198
|
+
badDecision: 'wrote the file without reading it',
|
|
199
|
+
betterDecision: BETTER_DECISION_MARKER,
|
|
200
|
+
rationale: 'reading first keeps edits consistent with on-disk state',
|
|
201
|
+
riskLevel: 'low',
|
|
202
|
+
strategicPerspective: 'safety',
|
|
203
|
+
}),
|
|
204
|
+
});
|
|
205
|
+
await mk(PHIL_ID, 'philosopher', [DREAM_ID]);
|
|
206
|
+
await upsertArtifact(PHIL_ART, PHIL_ID, [DREAM_ART], {
|
|
207
|
+
thesis: 'read before write', principleCandidate: { title: 'Read before write', scope: 'file tools', confidence: 0.8 },
|
|
208
|
+
});
|
|
209
|
+
await mk(SCRIBE_ID, 'scribe', [PHIL_ID]);
|
|
210
|
+
const scribeStatement = 'Read a file before writing it.';
|
|
211
|
+
await upsertArtifact(SCRIBE_ART, SCRIBE_ID, [PHIL_ART], {
|
|
212
|
+
principleId: 'pri-sip-read-before-write',
|
|
213
|
+
principleDraft: { statement: scribeStatement, applicability: ['file tools'], antiPatterns: ['blind overwrite'] },
|
|
214
|
+
sourceTrace: {},
|
|
215
|
+
// Marker that exists ONLY in the full contentJson — a focused manifest
|
|
216
|
+
// injection must never carry it (information-floor guard, SPEC §37).
|
|
217
|
+
scratchNote: SCRIBE_RAW_ONLY_MARKER,
|
|
218
|
+
summary: summaryEnvelope('scribe', scribeStatement, {
|
|
219
|
+
principleText: scribeStatement,
|
|
220
|
+
scope: '["file tools"]',
|
|
221
|
+
exceptions: '["blind overwrite"]',
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function meta(o = {}) {
|
|
226
|
+
return createPITaskDiagnosticJson({
|
|
227
|
+
dependencyTaskIds: [], channel: 'prompt', timeoutMs: 300_000,
|
|
228
|
+
inputArtifactRefs: [], outputArtifactRefs: [], ...o,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** Scripted LLM adapter; records prompts and returns queued outputs in order. */
|
|
232
|
+
function scriptedAdapter(outputs, prompts, runId) {
|
|
233
|
+
let call = 0;
|
|
234
|
+
return {
|
|
235
|
+
startRun: async (req) => {
|
|
236
|
+
prompts.push(String(req.inputPayload));
|
|
237
|
+
return { runId: `${runId}-${call++}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
|
|
238
|
+
},
|
|
239
|
+
pollRun: async () => ({ status: 'succeeded', runId }),
|
|
240
|
+
fetchOutput: async () => ({ runId, payload: outputs.shift() }),
|
|
241
|
+
cancelRun: async () => undefined,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function artificerOutput(implementationCode, scribeArtifactId, taskId) {
|
|
245
|
+
return {
|
|
246
|
+
taskId,
|
|
247
|
+
sourceScribeArtifactId: scribeArtifactId,
|
|
248
|
+
implementationSummary: 'read-before-write guard',
|
|
249
|
+
sourceTrace: { scribeArtifactId },
|
|
250
|
+
risks: [],
|
|
251
|
+
implementationCode,
|
|
252
|
+
goldenTraceCases: GOLDEN_TRACE_CASES,
|
|
253
|
+
affectedTools: ['write_file'],
|
|
254
|
+
generatedAt: new Date().toISOString(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/** Artificer artifact content with the Layer 0 envelope the runner itself
|
|
258
|
+
* attaches under artifact_summary_redundancy (see attach-summary-envelope). */
|
|
259
|
+
function artificerArtifactContent(output, scribeArtifactId) {
|
|
260
|
+
return {
|
|
261
|
+
...output,
|
|
262
|
+
summary: summaryEnvelope('artificer', '1 affected tools / read-before-write guard', {
|
|
263
|
+
changedFiles: '["write_file"]',
|
|
264
|
+
apiSurface: 'read-before-write guard',
|
|
265
|
+
risks: '[]',
|
|
266
|
+
}),
|
|
267
|
+
// Direct-predecessor forwarding: the scribe's summary, one edge up.
|
|
268
|
+
predecessorSummary: {
|
|
269
|
+
artifactId: scribeArtifactId,
|
|
270
|
+
runnerKind: 'scribe',
|
|
271
|
+
contentHash: 'test-hash',
|
|
272
|
+
summary: summaryEnvelope('scribe', 'Read a file before writing it.', {
|
|
273
|
+
principleText: 'Read a file before writing it.',
|
|
274
|
+
scope: '["file tools"]',
|
|
275
|
+
exceptions: '["blind overwrite"]',
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** `evaluationExtra` is merged INSIDE `evaluation` (where the schema and the
|
|
281
|
+
* PRI-630 convergence validator read them); `extra` stays top-level. */
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- compact regression-fixture DSL: positional (id, kind, content) reads better at 20 call sites than an options object; same rule tolerance as attach-summary-envelope.ts
|
|
283
|
+
function evaluatorOutput(taskId, artificerArtifactId, decision, extra = {}, evaluationExtra = {}) {
|
|
284
|
+
return {
|
|
285
|
+
taskId,
|
|
286
|
+
sourceArtificerArtifactId: artificerArtifactId,
|
|
287
|
+
evaluation: {
|
|
288
|
+
decision,
|
|
289
|
+
summary: 'sip regression',
|
|
290
|
+
score: decision === 'approved' ? 0.9 : 0.6,
|
|
291
|
+
strengths: [],
|
|
292
|
+
concerns: decision === 'needs_revision' ? ['paramsSummary misuse'] : [],
|
|
293
|
+
requiredChanges: decision === 'needs_revision' ? ['Fix the paramsSummary string-method crash'] : [],
|
|
294
|
+
...evaluationExtra,
|
|
295
|
+
},
|
|
296
|
+
sourceTrace: { artificerArtifactId, scribeArtifactId: SCRIBE_ART },
|
|
297
|
+
risks: [],
|
|
298
|
+
generatedAt: new Date().toISOString(),
|
|
299
|
+
...extra,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** PRI-630 convergence block: round 2 adjudicates round 1's requirement. */
|
|
303
|
+
const ROUND2_CONVERGENCE = {
|
|
304
|
+
priorRequirementStatuses: [{ id: 'req-1', status: 'resolved' }],
|
|
305
|
+
requirementLedger: [{ id: 'req-1', statement: 'Fix the paramsSummary string-method crash', status: 'resolved' }],
|
|
306
|
+
};
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- compact regression-fixture DSL: positional (id, kind, content) reads better at 20 call sites than an options object; same rule tolerance as attach-summary-envelope.ts
|
|
308
|
+
function makeArtificerRunner(outputs, prompts, runId, config) {
|
|
309
|
+
return new ArtificerRunner({
|
|
310
|
+
stateManager,
|
|
311
|
+
runtimeAdapter: scriptedAdapter(outputs, prompts, runId),
|
|
312
|
+
eventEmitter: emitter,
|
|
313
|
+
artifactStore: store,
|
|
314
|
+
validator: new DefaultArtificerValidator(),
|
|
315
|
+
}, { owner: 'sip-test', runtimeKind: 'test-double', pollIntervalMs: 5, timeoutMs: 5_000, effectiveConfig: config });
|
|
316
|
+
}
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- compact regression-fixture DSL: positional (id, kind, content) reads better at 20 call sites than an options object; same rule tolerance as attach-summary-envelope.ts
|
|
318
|
+
function makeEvaluatorRunner(outputs, prompts, runId, config, seeder) {
|
|
319
|
+
return new EvaluatorRunner({
|
|
320
|
+
stateManager,
|
|
321
|
+
runtimeAdapter: scriptedAdapter(outputs, prompts, runId),
|
|
322
|
+
eventEmitter: emitter,
|
|
323
|
+
artifactStore: store,
|
|
324
|
+
validator: new DefaultEvaluatorValidator(),
|
|
325
|
+
isRepairLoopEnabled: () => true,
|
|
326
|
+
...(seeder !== undefined ? { seedArtificerRepairTask: seeder } : {}),
|
|
327
|
+
}, {
|
|
328
|
+
owner: 'sip-test', runtimeKind: 'test-double', pollIntervalMs: 5, timeoutMs: 5_000,
|
|
329
|
+
gateDeps: createProductionGateDeps(), // REAL deterministic replay — no stubs
|
|
330
|
+
effectiveConfig: config,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
beforeEach(async () => {
|
|
334
|
+
workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-sip-'));
|
|
335
|
+
stateManager = new RuntimeStateManager({ workspaceDir });
|
|
336
|
+
await stateManager.initialize();
|
|
337
|
+
emitter = new StoreEventEmitter();
|
|
338
|
+
emitted = [];
|
|
339
|
+
emitter.onTelemetry((event) => {
|
|
340
|
+
emitted.push({ eventType: event.eventType, payload: event.payload });
|
|
341
|
+
});
|
|
342
|
+
store = new SqlitePIArtifactStore(new SqliteConnection(workspaceDir));
|
|
343
|
+
});
|
|
344
|
+
afterEach(async () => {
|
|
345
|
+
await stateManager.close();
|
|
346
|
+
try {
|
|
347
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
catch { /* temp */ }
|
|
350
|
+
});
|
|
351
|
+
describe('PR B T-A: normal Artificer resolves tier2 through durable CandidateLineage (flags ON)', () => {
|
|
352
|
+
it('focused prompt carries dreamer.raw.candidates.0.* from the durable ancestry — and no repair/replay pollution', async () => {
|
|
353
|
+
await seedInternalizationChain();
|
|
354
|
+
await stateManager.createTask({
|
|
355
|
+
taskId: ART1_ID, taskKind: 'artificer', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
356
|
+
diagnosticJson: meta({ dependencyTaskIds: [SCRIBE_ID] }),
|
|
357
|
+
});
|
|
358
|
+
const prompts = [];
|
|
359
|
+
const artificer = makeArtificerRunner([artificerOutput(GOOD_RULE_CODE, SCRIBE_ART, ART1_ID)], prompts, 'run-sip-art', FLAGS_ON);
|
|
360
|
+
const result = await artificer.run(ART1_ID);
|
|
361
|
+
expect(result.status).toBe('succeeded');
|
|
362
|
+
expect(prompts).toHaveLength(1);
|
|
363
|
+
const [prompt] = prompts;
|
|
364
|
+
if (prompt === undefined)
|
|
365
|
+
return;
|
|
366
|
+
// tier2 resolved from the DURABLE Dreamer artifact via CandidateLineage:
|
|
367
|
+
// the path key and the actual raw value both reach the prompt.
|
|
368
|
+
expect(prompt).toContain('dreamer.raw.candidates.0.betterDecision');
|
|
369
|
+
expect(prompt).toContain(BETTER_DECISION_MARKER);
|
|
370
|
+
expect(prompt).toContain('dreamer.raw.candidates.0.rationale');
|
|
371
|
+
// tier1 resolved from the Layer 0 summary envelope on the scribe artifact.
|
|
372
|
+
expect(prompt).toContain('scribe.summary.principleText');
|
|
373
|
+
expect(prompt).toContain('Read a file before writing it.');
|
|
374
|
+
// Information floor: focused mode must NOT leak fields outside the manifest
|
|
375
|
+
// (the legacy full-contentJson injection would have carried scratchNote).
|
|
376
|
+
expect(prompt).not.toContain(SCRIBE_RAW_ONLY_MARKER);
|
|
377
|
+
// Normal Artificer is not polluted by repair-only namespaces (SPEC §30).
|
|
378
|
+
expect(prompt).not.toContain('replay.raw');
|
|
379
|
+
expect(prompt).not.toContain('repair.summary');
|
|
380
|
+
// No information-floor degradation fired: resolution was healthy.
|
|
381
|
+
// (emitEvent prefixes event types with the runnerName.)
|
|
382
|
+
expect(emitted.some((e) => e.eventType === 'artificer_manifest_resolution_insufficient')).toBe(false);
|
|
383
|
+
expect(emitted.some((e) => e.eventType === 'artificer_required_context_evidence_unresolved')).toBe(false);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
/** Shared T-B family setup: durable Round-1 Artificer artifact + Evaluator task. */
|
|
387
|
+
async function seedEvaluatorChain() {
|
|
388
|
+
await stateManager.createTask({
|
|
389
|
+
taskId: ART1_ID, taskKind: 'artificer', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
390
|
+
diagnosticJson: meta({ dependencyTaskIds: [SCRIBE_ID] }),
|
|
391
|
+
});
|
|
392
|
+
await stateManager.acquireLease({ taskId: ART1_ID, owner: 'sip-test', runtimeKind: 'test-double' });
|
|
393
|
+
await stateManager.markTaskSucceeded(ART1_ID);
|
|
394
|
+
await upsertArtifact(ART1_ART, ART1_ID, [SCRIBE_ART], artificerArtifactContent(artificerOutput(GOOD_RULE_CODE, SCRIBE_ART, ART1_ID), SCRIBE_ART));
|
|
395
|
+
await stateManager.createTask({
|
|
396
|
+
taskId: EVAL1_ID, taskKind: 'evaluator', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
397
|
+
diagnosticJson: meta({ dependencyTaskIds: [ART1_ID] }),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
describe('PR B T-B: Evaluator Stage2 resolves required tier2 raw evidence (flags ON)', () => {
|
|
401
|
+
it('Stage2 prompt carries diagnostician.raw.evidence + dreamer.raw.candidates from the real split lineage', async () => {
|
|
402
|
+
// Default split-pipeline identity: the diag artifact node is taskKind
|
|
403
|
+
// `diag_router` (SplitDiagnosticianRunner stage C) — the manifest
|
|
404
|
+
// namespace `diagnostician` must resolve to it via SEMANTIC_STAGE_ALIASES.
|
|
405
|
+
await seedInternalizationChain('diag_router');
|
|
406
|
+
await seedEvaluatorChain();
|
|
407
|
+
// Stage 1 output: contract-valid, but flagged (painCoverage.fullyCovered=false)
|
|
408
|
+
// → Stage 2 must run. Stage 2 output: approved → real replay passes → pi-rule.
|
|
409
|
+
const stage1Output = evaluatorOutput(EVAL1_ID, ART1_ART, 'approved', {
|
|
410
|
+
painCoverage: { fullyCovered: false },
|
|
411
|
+
compressionFidelity: { missingDimensions: [] },
|
|
412
|
+
});
|
|
413
|
+
const stage2Output = evaluatorOutput(EVAL1_ID, ART1_ART, 'approved');
|
|
414
|
+
const prompts = [];
|
|
415
|
+
const evaluator = makeEvaluatorRunner([stage1Output, stage2Output], prompts, 'run-sip-eval', FLAGS_ON);
|
|
416
|
+
const result = await evaluator.run(EVAL1_ID);
|
|
417
|
+
expect(result.status).toBe('succeeded');
|
|
418
|
+
// Two LLM calls = Stage 2 actually ran (not just Stage 1).
|
|
419
|
+
expect(prompts.length).toBe(2);
|
|
420
|
+
const [stage1Prompt, stage2Prompt] = prompts;
|
|
421
|
+
// Stage 1 stayed summary-level: no tier2 raw path and no raw-only value.
|
|
422
|
+
// (`dreamer.summary.betterDecision` legitimately carries the same marker
|
|
423
|
+
// string — the discriminator is the RAW path/value, not the marker alone.)
|
|
424
|
+
expect(stage1Prompt).not.toContain('dreamer.raw.candidates');
|
|
425
|
+
expect(stage1Prompt).not.toContain('diagnostician.raw.evidence');
|
|
426
|
+
expect(stage1Prompt).not.toContain(PAIN_EVIDENCE_MARKER);
|
|
427
|
+
// Stage 1 resolves ANCESTOR summaries (scribe/dreamer) + the read-time
|
|
428
|
+
// projected diagnostician summary through the ancestry channel instead of
|
|
429
|
+
// falling back — the namespace-unreachability that used to force
|
|
430
|
+
// `manifest_resolution_insufficient` on every run is gone.
|
|
431
|
+
expect(emitted.some((e) => e.eventType === 'evaluator_manifest_resolution_insufficient'
|
|
432
|
+
&& e.payload.manifestId === 'evaluator.stage1.v1')).toBe(false);
|
|
433
|
+
expect(stage1Prompt).toContain('dreamer.summary.betterDecision');
|
|
434
|
+
expect(stage1Prompt).toContain(BETTER_DECISION_MARKER);
|
|
435
|
+
// Pain context is genuinely present at Stage 1 (bounded read-time
|
|
436
|
+
// projection of the diag_router DiagnosticianOutputV1) — never a
|
|
437
|
+
// "known always absent" contract (review round).
|
|
438
|
+
expect(stage1Prompt).toContain('diagnostician.summary.rootSymptom');
|
|
439
|
+
expect(stage1Prompt).toContain('Agent wrote a controlled file without reading it first.');
|
|
440
|
+
expect(stage1Prompt).toContain('diagnostician.summary.category');
|
|
441
|
+
expect(stage1Prompt).toContain('internalize');
|
|
442
|
+
// Stage 2 REQUIRED tier2 fields resolved from durable ancestry — the
|
|
443
|
+
// split-chain diag_router node answers the `diagnostician` namespace.
|
|
444
|
+
expect(stage2Prompt).toContain('dreamer.raw.candidates');
|
|
445
|
+
expect(stage2Prompt).toContain(BETTER_DECISION_MARKER);
|
|
446
|
+
expect(stage2Prompt).toContain('diagnostician.raw.evidence');
|
|
447
|
+
expect(stage2Prompt).toContain(PAIN_EVIDENCE_MARKER);
|
|
448
|
+
// Information floor: no silent required-evidence loss on Stage 2.
|
|
449
|
+
expect(emitted.some((e) => e.eventType === 'evaluator_required_context_evidence_unresolved'
|
|
450
|
+
&& e.payload.manifestId === 'evaluator.stage2.v1')).toBe(false);
|
|
451
|
+
// The loop still closes: real replay passed → pi-rule assembled.
|
|
452
|
+
const evalArtifacts = await store.listBySourceTaskId(EVAL1_ID);
|
|
453
|
+
const ruleArtifact = evalArtifacts.find((a) => a.artifactKind === 'rule');
|
|
454
|
+
expect(ruleArtifact).toBeDefined();
|
|
455
|
+
if (ruleArtifact)
|
|
456
|
+
expect(ruleArtifact.artifactId).toContain('pi-rule-');
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
describe('PR B T-B3: legacy monolithic diagnostician identity resolves the same namespace', () => {
|
|
460
|
+
it('Stage2 evidence also resolves when the diag node taskKind is `diagnostician`', async () => {
|
|
461
|
+
// Legacy (pre-split / old workspace) identity: durable DiagnosticianOutputV1
|
|
462
|
+
// committed under taskKind `diagnostician`. The alias table must cover it.
|
|
463
|
+
await seedInternalizationChain('diagnostician');
|
|
464
|
+
await seedEvaluatorChain();
|
|
465
|
+
const prompts = [];
|
|
466
|
+
const evaluator = makeEvaluatorRunner([
|
|
467
|
+
evaluatorOutput(EVAL1_ID, ART1_ART, 'approved', {
|
|
468
|
+
painCoverage: { fullyCovered: false },
|
|
469
|
+
compressionFidelity: { missingDimensions: [] },
|
|
470
|
+
}),
|
|
471
|
+
evaluatorOutput(EVAL1_ID, ART1_ART, 'approved'),
|
|
472
|
+
], prompts, 'run-sip-eval-legacy', FLAGS_ON);
|
|
473
|
+
expect((await evaluator.run(EVAL1_ID)).status).toBe('succeeded');
|
|
474
|
+
expect(prompts.length).toBe(2);
|
|
475
|
+
const stage2Prompt = prompts[1] ?? '';
|
|
476
|
+
expect(stage2Prompt).toContain('diagnostician.raw.evidence');
|
|
477
|
+
expect(stage2Prompt).toContain(PAIN_EVIDENCE_MARKER);
|
|
478
|
+
expect(emitted.some((e) => e.eventType === 'evaluator_required_context_evidence_unresolved'
|
|
479
|
+
&& e.payload.manifestId === 'evaluator.stage2.v1')).toBe(false);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
/**
|
|
483
|
+
* T-B2 — the information floor in its negative form (design §34/§35, review
|
|
484
|
+
* round). Same run as T-B, but the durable diagnostician EVIDENCE is missing.
|
|
485
|
+
* Stage 2 declares `diagnostician.raw.evidence` REQUIRED; since Stage 2 is the
|
|
486
|
+
* deep-evidence stage there is NO safe legacy fallback that carries the
|
|
487
|
+
* missing evidence — the runner must REFUSE the Stage-2 LLM round entirely and
|
|
488
|
+
* fail loud. The scripted Stage-2 output WOULD approve; the assertion is that
|
|
489
|
+
* it is never consumed.
|
|
490
|
+
*/
|
|
491
|
+
describe('PR B T-B2: Stage2 required evidence missing → no Stage2 LLM, fail loud (never a silent verdict)', () => {
|
|
492
|
+
it('emits required_context_evidence_unresolved + abort, sends exactly ONE prompt, and assembles no pi-rule', async () => {
|
|
493
|
+
await seedInternalizationChain('diag_router');
|
|
494
|
+
// Strip ONLY the durable diagnostician evidence: the task node and the
|
|
495
|
+
// artifact stay, so the lineage walk succeeds but cannot serve the
|
|
496
|
+
// required raw path (a corruption short of a missing row).
|
|
497
|
+
await upsertArtifact(DIAG_ART, DIAG_ID, [], {
|
|
498
|
+
valid: true, diagnosisId: 'diag-sip-1',
|
|
499
|
+
summary: 'Agent wrote a controlled file without reading it first.',
|
|
500
|
+
rootCause: 'Tooling: write path skipped the read-before-write check.',
|
|
501
|
+
violatedPrinciples: [], recommendations: [], confidence: 0.9,
|
|
502
|
+
});
|
|
503
|
+
await seedEvaluatorChain();
|
|
504
|
+
const prompts = [];
|
|
505
|
+
const evaluator = makeEvaluatorRunner([
|
|
506
|
+
evaluatorOutput(EVAL1_ID, ART1_ART, 'approved', { painCoverage: { fullyCovered: false } }),
|
|
507
|
+
// WOULD approve — must NEVER be consumed (regression guard).
|
|
508
|
+
evaluatorOutput(EVAL1_ID, ART1_ART, 'approved'),
|
|
509
|
+
], prompts, 'run-sip-eval-floor', FLAGS_ON);
|
|
510
|
+
const result = await evaluator.run(EVAL1_ID);
|
|
511
|
+
// Fail loud, permanent, NO authoritative verdict.
|
|
512
|
+
expect(result.status).toBe('failed');
|
|
513
|
+
expect(result.errorCategory).toBe('input_invalid');
|
|
514
|
+
// The gate FIRED (information floor) AND the abort is observable.
|
|
515
|
+
const gate = emitted.find((e) => e.eventType === 'evaluator_required_context_evidence_unresolved');
|
|
516
|
+
expect(gate).toBeDefined();
|
|
517
|
+
expect(gate?.payload.manifestId).toBe('evaluator.stage2.v1');
|
|
518
|
+
expect(gate?.payload.requiredPaths).toEqual(['diagnostician.raw.evidence']);
|
|
519
|
+
const abort = emitted.find((e) => e.eventType === 'evaluator_stage2_required_evidence_unavailable');
|
|
520
|
+
expect(abort).toBeDefined();
|
|
521
|
+
expect(abort?.payload.requiredPaths).toEqual(['diagnostician.raw.evidence']);
|
|
522
|
+
// Stage 2 LLM was NEVER sent: exactly 1 prompt (Stage 1 only).
|
|
523
|
+
expect(prompts.length).toBe(1);
|
|
524
|
+
// No authoritative verdict artifact of any kind was produced.
|
|
525
|
+
const evalArtifacts = await store.listBySourceTaskId(EVAL1_ID);
|
|
526
|
+
expect(evalArtifacts.find((a) => a.artifactKind === 'rule')).toBeUndefined();
|
|
527
|
+
expect(evalArtifacts.find((a) => a.artifactKind === 'principle')).toBeUndefined();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
describe('PR B T-C: Artificer Repair receives replay evidence through the RELATED manifest channel (flags ON)', () => {
|
|
531
|
+
it('repair prompt carries bounded replay evidence via ARTIFICER_REPAIR_MANIFEST; loop still closes to pi-rule', async () => {
|
|
532
|
+
await seedInternalizationChain();
|
|
533
|
+
// ── Round 1: real artificer emits the paramsSummary bug (flags ON) ──
|
|
534
|
+
await stateManager.createTask({
|
|
535
|
+
taskId: ART1_ID, taskKind: 'artificer', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
536
|
+
diagnosticJson: meta({ dependencyTaskIds: [SCRIBE_ID] }),
|
|
537
|
+
});
|
|
538
|
+
const art1Prompts = [];
|
|
539
|
+
const artificer1 = makeArtificerRunner([artificerOutput(BAD_RULE_CODE, SCRIBE_ART, ART1_ID)], art1Prompts, 'run-sip-art1', FLAGS_ON);
|
|
540
|
+
expect((await artificer1.run(ART1_ID)).status).toBe('succeeded');
|
|
541
|
+
const art1Artifacts = await store.listBySourceTaskId(ART1_ID);
|
|
542
|
+
const art1Artifact = art1Artifacts.find((a) => a.artifactKind === 'principle');
|
|
543
|
+
expect(art1Artifact).toBeDefined();
|
|
544
|
+
if (!art1Artifact)
|
|
545
|
+
return;
|
|
546
|
+
// ── Evaluator needs_revision + REAL replay → durable failure evidence ──
|
|
547
|
+
await stateManager.createTask({
|
|
548
|
+
taskId: EVAL1_ID, taskKind: 'evaluator', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
549
|
+
diagnosticJson: meta({ dependencyTaskIds: [ART1_ID] }),
|
|
550
|
+
});
|
|
551
|
+
const seededRepair = {};
|
|
552
|
+
const eval1Prompts = [];
|
|
553
|
+
// Progressive mode consumes TWO outputs: Stage 1 (flagged via painCoverage
|
|
554
|
+
// → deterministic Stage 2 trigger) and Stage 2 (the final needs_revision).
|
|
555
|
+
const evalNeedsRevision = evaluatorOutput(EVAL1_ID, art1Artifact.artifactId, 'needs_revision', {
|
|
556
|
+
painCoverage: { fullyCovered: false },
|
|
557
|
+
compressionFidelity: { missingDimensions: [] },
|
|
558
|
+
});
|
|
559
|
+
const evaluator1 = makeEvaluatorRunner([evalNeedsRevision, evaluatorOutput(EVAL1_ID, art1Artifact.artifactId, 'needs_revision')], eval1Prompts, 'run-sip-eval1', FLAGS_ON, async (params) => {
|
|
560
|
+
seededRepair.payload = params.repairPayload;
|
|
561
|
+
await stateManager.createTask({
|
|
562
|
+
taskId: REPAIR_ID, taskKind: 'artificer', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
563
|
+
diagnosticJson: meta({
|
|
564
|
+
dependencyTaskIds: params.inheritedDependencyTaskIds,
|
|
565
|
+
repairPayload: params.repairPayload,
|
|
566
|
+
}),
|
|
567
|
+
});
|
|
568
|
+
return REPAIR_ID;
|
|
569
|
+
});
|
|
570
|
+
expect((await evaluator1.run(EVAL1_ID)).status).toBe('succeeded');
|
|
571
|
+
// RepairPayload stays a bounded control payload (no failedCases, SPEC §21).
|
|
572
|
+
const repairPayload = seededRepair.payload;
|
|
573
|
+
expect(repairPayload).toBeDefined();
|
|
574
|
+
if (repairPayload === undefined)
|
|
575
|
+
return;
|
|
576
|
+
expect(repairPayload.diagnosticReplay).toEqual({ ran: true, passed: false, failedCaseCount: expect.any(Number) });
|
|
577
|
+
expect(Object.hasOwn(repairPayload, 'failedCases')).toBe(false);
|
|
578
|
+
// ── Repair round: replay evidence arrives via the manifest RELATED channel ──
|
|
579
|
+
const repairPrompts = [];
|
|
580
|
+
const artificerRepair = makeArtificerRunner([artificerOutput(GOOD_RULE_CODE, SCRIBE_ART, REPAIR_ID)], repairPrompts, 'run-sip-repair', FLAGS_ON);
|
|
581
|
+
expect((await artificerRepair.run(REPAIR_ID)).status).toBe('succeeded');
|
|
582
|
+
expect(repairPrompts).toHaveLength(1);
|
|
583
|
+
const [repairPrompt] = repairPrompts;
|
|
584
|
+
if (repairPrompt === undefined)
|
|
585
|
+
return;
|
|
586
|
+
// RELATED channel: manifest field paths + the concrete case details.
|
|
587
|
+
expect(repairPrompt).toContain('replay.raw.traceFailures');
|
|
588
|
+
expect(repairPrompt).toContain('replay.summary.failedCaseCount');
|
|
589
|
+
expect(repairPrompt).toContain('repair.summary.requiredChanges');
|
|
590
|
+
expect(repairPrompt).toContain('v2-unavailable');
|
|
591
|
+
expect(repairPrompt).toContain('runtime_error');
|
|
592
|
+
expect(repairPrompt).toContain('includes is not a function');
|
|
593
|
+
// The PR-A string channel is NOT also injected (no double evidence).
|
|
594
|
+
expect(repairPrompt).not.toContain('Deterministic Replay Evidence (resolved by reference from evaluator artifact');
|
|
595
|
+
// PR-A resolver was reused (not duplicated) and resolved by reference.
|
|
596
|
+
expect(emitted.some((e) => e.eventType === 'artificer_repair_replay_evidence_resolved')).toBe(true);
|
|
597
|
+
// Required-evidence gate did not fire: the budget carried the evidence.
|
|
598
|
+
expect(emitted.some((e) => e.eventType === 'required_context_evidence_unresolved'
|
|
599
|
+
&& e.payload.manifestId === 'artificer.repair.v1')).toBe(false);
|
|
600
|
+
// ── Round 2: evaluator approves the repaired code → pi-rule ──
|
|
601
|
+
const repairArtifacts = await store.listBySourceTaskId(REPAIR_ID);
|
|
602
|
+
const repairArtifact = repairArtifacts.find((a) => a.artifactKind === 'principle');
|
|
603
|
+
expect(repairArtifact).toBeDefined();
|
|
604
|
+
if (!repairArtifact)
|
|
605
|
+
return;
|
|
606
|
+
await stateManager.createTask({
|
|
607
|
+
taskId: EVAL2_ID, taskKind: 'evaluator', status: 'pending', attemptCount: 0, maxAttempts: 3,
|
|
608
|
+
diagnosticJson: meta({ dependencyTaskIds: [REPAIR_ID] }),
|
|
609
|
+
});
|
|
610
|
+
const eval2Prompts = [];
|
|
611
|
+
const evaluator2 = makeEvaluatorRunner([evaluatorOutput(EVAL2_ID, repairArtifact.artifactId, 'approved', {
|
|
612
|
+
// Not flagged and nothing undetermined → Stage 1 alone decides, so
|
|
613
|
+
// this evaluator consumes exactly ONE LLM output (no Stage 2).
|
|
614
|
+
painCoverage: { fullyCovered: true },
|
|
615
|
+
compressionFidelity: { missingDimensions: [] },
|
|
616
|
+
implementationFidelity: { score: 0.92 },
|
|
617
|
+
}, ROUND2_CONVERGENCE)], eval2Prompts, 'run-sip-eval2', FLAGS_ON);
|
|
618
|
+
expect((await evaluator2.run(EVAL2_ID)).status).toBe('succeeded');
|
|
619
|
+
const eval2Artifacts = await store.listBySourceTaskId(EVAL2_ID);
|
|
620
|
+
const ruleArtifact = eval2Artifacts.find((a) => a.artifactKind === 'rule');
|
|
621
|
+
expect(ruleArtifact).toBeDefined();
|
|
622
|
+
if (ruleArtifact) {
|
|
623
|
+
expect(ruleArtifact.artifactId).toContain('pi-rule-');
|
|
624
|
+
expect(ruleArtifact.validationStatus).toBe('validated');
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
//# sourceMappingURL=shared-information-plane-runner.test.js.map
|