principles-disciple 1.121.0 → 1.123.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.
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.121.0",
5
+ "version": "1.123.0",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.121.0",
3
+ "version": "1.123.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -6,6 +6,7 @@ import {
6
6
  DefaultDreamerValidator,
7
7
  PiAiRuntimeAdapter,
8
8
  L2AgentLoopAdapter,
9
+ buildL2PrincipleReaderFromLedger,
9
10
  loadLedger,
10
11
  OpenClawCliRuntimeAdapter,
11
12
  storeEmitter,
@@ -15,7 +16,6 @@ import {
15
16
  InternalizationQueueReadModel,
16
17
  MVP_CORE_TASK_KINDS,
17
18
  type PDRuntimeAdapter,
18
- type PdL2PrincipleReader,
19
19
  } from '@principles/core/runtime-v2';
20
20
  import { loadPdConfigForPlugin, loadFeatureFlagFromConfig } from '../core/pd-config-loader.js';
21
21
  import { SystemLogger } from '../core/system-logger.js';
@@ -176,21 +176,9 @@ export async function runConsumerCycle(
176
176
  const l2Flag = loadFeatureFlagFromConfig(workspaceDir, 'l2_dreamer');
177
177
  if (l2Flag.enabled) {
178
178
  const stateDir = `${workspaceDir}/.state`;
179
- const principleReader: PdL2PrincipleReader = {
180
- listActivePrinciples: async () => {
181
- try {
182
- const ledger = loadLedger(stateDir);
183
- const principles = ledger.tree.principles ?? {};
184
- return Object.values(principles)
185
- .filter(p => p.status === 'active' && typeof p.id === 'string' && typeof p.text === 'string')
186
- .map(p => ({ id: p.id, statement: p.text }));
187
- } catch (error) {
188
- const reason = error instanceof Error ? error.message : String(error);
189
- logger.warn(`[PD:AutoConsumer] L2 dreamer principle reader degraded: ${reason}`);
190
- return [];
191
- }
192
- },
193
- };
179
+ const principleReader = buildL2PrincipleReaderFromLedger(loadLedger(stateDir), {
180
+ logger: { warn: (msg) => logger.warn(msg) },
181
+ });
194
182
  adapter = new L2AgentLoopAdapter(
195
183
  {
196
184
  provider: runtimeConfigResult.provider ?? 'openai',
@@ -364,7 +364,7 @@ describe('PRI-288: EvolutionWorkerService quarantine', () => {
364
364
  expect(flags.flags['evolution_worker']?.enabled).toBe(true);
365
365
  });
366
366
 
367
- it('core flags cannot be disabled by user override', () => {
367
+ it('PRI-435: core flags can be explicitly emergency-disabled by user override with warning', () => {
368
368
  writeConfigYaml(workspaceDir, {
369
369
  prompt: { enabled: false },
370
370
  code_tool_hook: { enabled: false },
@@ -377,9 +377,11 @@ describe('PRI-288: EvolutionWorkerService quarantine', () => {
377
377
  expect(isRecord(parsed)).toBe(true);
378
378
  const features = (parsed as Record<string, unknown>).features;
379
379
  const flags = computeEffectiveFlags(features as Record<string, unknown>, DEFAULT_FEATURE_FLAGS, configPath);
380
- expect(flags.flags['prompt']?.enabled).toBe(true); // core cannot be disabled
381
- expect(flags.flags['code_tool_hook']?.enabled).toBe(true); // core cannot be disabled
382
- expect(flags.warnings.length).toBeGreaterThan(0); // warnings about core override attempt
380
+ // PRI-435: core flags honor explicit emergency disable when deliberately configured
381
+ expect(flags.flags['prompt']?.enabled).toBe(false);
382
+ expect(flags.flags['code_tool_hook']?.enabled).toBe(false);
383
+ expect(flags.warnings.length).toBeGreaterThan(0); // warnings about emergency disable
384
+ expect(flags.warnings.some(w => w.includes('core flag explicitly disabled'))).toBe(true);
383
385
  });
384
386
  });
385
387
 
@@ -0,0 +1,420 @@
1
+ /**
2
+ * PRI-433: PainAdmissionEmitter characterization tests (safety net).
3
+ *
4
+ * These tests capture the CURRENT behavior of the 4 hook sites that emit
5
+ * pain_detected events via emitPainDetectedEvent. They act as a safety net
6
+ * for a future extraction refactor (consolidating into a PainAdmissionEmitter).
7
+ *
8
+ * Scope: static source code analysis only. No runtime mocking needed.
9
+ * This follows the pattern established by runtime-v2-pain-guard.test.ts.
10
+ *
11
+ * Each section captures:
12
+ * - Pain ID format (regex)
13
+ * - Provenance field value
14
+ * - Required fields present/missing (documents inconsistencies)
15
+ * - Gate function used before emit
16
+ * - Emit conditions (what must be true for emit to proceed)
17
+ *
18
+ * Known inconsistencies (to be resolved by future extraction):
19
+ * | Site | painId format | provenance | evidence | traceId |
20
+ * |--------------------------|----------------------------------|---------------------------|----------|---------|
21
+ * | after-tool-call-helpers | pain_${ts}_${hash.slice(0,8)} | 'automatic_hook' | yes | yes |
22
+ * | prompt.ts (GFI) | empathy_gfi_${ts} | 'openclaw_context_bound' | yes | no |
23
+ * | prompt.ts (observer) | empathy_gfi_${ts} | 'openclaw_context_bound' | yes | no |
24
+ * | llm.ts | llm_${ts} | 'openclaw_context_bound' | yes | no |
25
+ * | gate-block-helper | gate_${ts}_${random} | MISSING | MISSING | MISSING |
26
+ *
27
+ * ERR refs:
28
+ * - ERR-009 (fail-loud): tests fail if emit pattern changes without update
29
+ * - ERR-006 (lineage consistency): documents traceId presence/absence per site
30
+ */
31
+
32
+ import { describe, expect, it } from 'vitest';
33
+ import * as fs from 'fs';
34
+ import * as path from 'path';
35
+
36
+ // ── Helpers ────────────────────────────────────────────────────────────────
37
+
38
+ function findRepoRoot(cwd: string): string {
39
+ let dir = cwd;
40
+ while (dir !== path.dirname(dir)) {
41
+ if (fs.existsSync(path.join(dir, '.git'))) {
42
+ return dir;
43
+ }
44
+ dir = path.dirname(dir);
45
+ }
46
+ throw new Error(`Could not find repo root from ${cwd} — .git directory not found in any parent`);
47
+ }
48
+
49
+ const repoRoot = findRepoRoot(process.cwd());
50
+
51
+ function read(relativePath: string): string {
52
+ return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
53
+ }
54
+
55
+ /**
56
+ * Extract the gate-block pain event object from source code.
57
+ * Uses brace-depth tracking so template-string braces inside the object
58
+ * do not prematurely terminate the match (ERR-009 false-positive guard).
59
+ */
60
+ function extractGateBlockObject(source: string): string | null {
61
+ const marker = /painId:\s*`gate_[^`]+`/.exec(source);
62
+ if (!marker) return null;
63
+ const start = source.lastIndexOf('{', marker.index);
64
+ if (start === -1) return null;
65
+ let depth = 1;
66
+ let i = start + 1;
67
+ while (i < source.length && depth > 0) {
68
+ const ch = source[i];
69
+ if (ch === '{') depth++;
70
+ else if (ch === '}') depth--;
71
+ i++;
72
+ }
73
+ if (depth !== 0) return null;
74
+ return source.slice(start, i);
75
+ }
76
+
77
+ // ── Source file paths ─────────────────────────────────────────────────────
78
+
79
+ const AFTER_TOOL_CALL_HELPERS = 'packages/openclaw-plugin/src/hooks/after-tool-call-helpers.ts';
80
+ const PROMPT = 'packages/openclaw-plugin/src/hooks/prompt.ts';
81
+ const LLM = 'packages/openclaw-plugin/src/hooks/llm.ts';
82
+ const GATE_BLOCK_HELPER = 'packages/openclaw-plugin/src/hooks/gate-block-helper.ts';
83
+
84
+ // ── Tests ─────────────────────────────────────────────────────────────────
85
+
86
+ describe('PRI-433: PainAdmissionEmitter characterization (safety net)', () => {
87
+
88
+ // ═══════════════════════════════════════════════════════════════════════
89
+ // Section 1: after-tool-call-helpers.ts — tool failure path
90
+ // ═══════════════════════════════════════════════════════════════════════
91
+
92
+ describe('after-tool-call-helpers.ts emit site', () => {
93
+ const source = read(AFTER_TOOL_CALL_HELPERS);
94
+
95
+ it('uses painId format: pain_${Date.now()}_${errorHash.slice(0,8)}', () => {
96
+ expect(source).toMatch(/painId\s*=\s*`pain_\$\{Date\.now\(\)\}_\$\{observation\.errorHash\.slice\(0,\s*8\)\}`/);
97
+ });
98
+
99
+ it('sets provenance to "automatic_hook"', () => {
100
+ expect(source).toMatch(/provenance:\s*'automatic_hook'/);
101
+ });
102
+
103
+ it('includes evidence field via buildTrajectoryEvidence', () => {
104
+ expect(source).toMatch(/evidence:\s*buildTrajectoryEvidence\(wctx,\s*sessionId\)/);
105
+ });
106
+
107
+ it('includes traceId from observation', () => {
108
+ expect(source).toMatch(/traceId:\s*observation\.traceId/);
109
+ });
110
+
111
+ it('sets painType to failureSource variable (tool_failure or dispatch_error)', () => {
112
+ expect(source).toMatch(/painType:\s*failureSource/);
113
+ });
114
+
115
+ it('sets agentId from context variable', () => {
116
+ expect(source).toMatch(/agentId,/);
117
+ });
118
+
119
+ it('uses evaluateTriggerController as gate (PRI-363 single gate)', () => {
120
+ expect(source).toMatch(/evaluateTriggerController/);
121
+ });
122
+
123
+ it('calls emitPainDetectedEvent (not legacy writePainFlag)', () => {
124
+ expect(source).toMatch(/emitPainDetectedEvent\(wctx,/);
125
+ expect(source).not.toMatch(/\bwritePainFlag\b/);
126
+ });
127
+
128
+ it('early-returns when admission.admitted is false', () => {
129
+ expect(source).toMatch(/if\s*\(!admission\.admitted\)\s*return/);
130
+ });
131
+ });
132
+
133
+ // ═══════════════════════════════════════════════════════════════════════
134
+ // Section 2: prompt.ts — empathy GFI path (2 instances)
135
+ // ═══════════════════════════════════════════════════════════════════════
136
+
137
+ describe('prompt.ts emit sites (2 empathy GFI instances)', () => {
138
+ const source = read(PROMPT);
139
+
140
+ it('uses painId format: empathy_gfi_${Date.now()} (both instances)', () => {
141
+ const matches = source.match(/painId:\s*`empathy_gfi_\$\{Date\.now\(\)\}`/g);
142
+ expect(matches).not.toBeNull();
143
+ expect(matches!.length).toBe(2);
144
+ });
145
+
146
+ it('sets provenance to "openclaw_context_bound" (both instances)', () => {
147
+ const matches = source.match(/provenance:\s*'openclaw_context_bound'/g);
148
+ expect(matches).not.toBeNull();
149
+ expect(matches!.length).toBeGreaterThanOrEqual(2);
150
+ });
151
+
152
+ it('sets painType to "user_frustration" (both instances)', () => {
153
+ const matches = source.match(/painType:\s*'user_frustration'/g);
154
+ expect(matches).not.toBeNull();
155
+ expect(matches!.length).toBeGreaterThanOrEqual(2);
156
+ });
157
+
158
+ it('sets source to "user_empathy" (both instances)', () => {
159
+ const matches = source.match(/source:\s*'user_empathy'/g);
160
+ expect(matches).not.toBeNull();
161
+ expect(matches!.length).toBeGreaterThanOrEqual(2);
162
+ });
163
+
164
+ it('hardcodes agentId to "main" (both instances)', () => {
165
+ const matches = source.match(/agentId:\s*'main'/g);
166
+ expect(matches).not.toBeNull();
167
+ expect(matches!.length).toBeGreaterThanOrEqual(2);
168
+ });
169
+
170
+ it('includes evidence field via buildTrajectoryEvidence (both instances)', () => {
171
+ const matches = source.match(/evidence,\s*\n\s*}/g);
172
+ expect(matches).not.toBeNull();
173
+ expect(matches!.length).toBeGreaterThanOrEqual(2);
174
+ });
175
+
176
+ it('does NOT include traceId field (known inconsistency)', () => {
177
+ // Extract the data blocks for empathy emit and verify no traceId.
178
+ // Use a non-greedy dot-all match to avoid truncation at template literal braces like ${Date.now()}
179
+ const empathyBlocks = source.match(/painId:\s*`empathy_gfi_[^`]*`[\s\S]*?^\s{8}\},?/gm);
180
+ expect(empathyBlocks).not.toBeNull();
181
+ for (const block of empathyBlocks!) {
182
+ expect(block).not.toMatch(/traceId/);
183
+ }
184
+ });
185
+
186
+ it('uses evaluatePainDiagnosticGate as gate (not evaluateTriggerController)', () => {
187
+ expect(source).toMatch(/evaluatePainDiagnosticGate/);
188
+ expect(source).not.toMatch(/evaluateTriggerController/);
189
+ });
190
+
191
+ it('gates emit on gate.shouldDiagnose being true', () => {
192
+ expect(source).toMatch(/if\s*\(gate\.shouldDiagnose\)/);
193
+ });
194
+
195
+ it('calls emitPainDetectedEvent with await (not fire-and-forget)', () => {
196
+ expect(source).toMatch(/await\s+emitPainDetectedEvent/);
197
+ });
198
+
199
+ it('wraps emit in try/catch for error handling', () => {
200
+ expect(source).toMatch(/try\s*\{[\s\S]*?await\s+emitPainDetectedEvent[\s\S]*?\}\s*catch\s*\(emitErr\)/);
201
+ });
202
+ });
203
+
204
+ // ═══════════════════════════════════════════════════════════════════════
205
+ // Section 3: llm.ts — semantic pain detection
206
+ // ═══════════════════════════════════════════════════════════════════════
207
+
208
+ describe('llm.ts emit site', () => {
209
+ const source = read(LLM);
210
+
211
+ it('uses painId format: llm_${Date.now()}', () => {
212
+ expect(source).toMatch(/painId:\s*`llm_\$\{Date\.now\(\)\}`/);
213
+ });
214
+
215
+ it('sets provenance to "openclaw_context_bound"', () => {
216
+ expect(source).toMatch(/provenance:\s*'openclaw_context_bound'/);
217
+ });
218
+
219
+ it('sets painType to "user_frustration" (as const)', () => {
220
+ expect(source).toMatch(/painType:\s*'user_frustration'\s+as\s+const/);
221
+ });
222
+
223
+ it('includes evidence field via buildTrajectoryEvidence', () => {
224
+ expect(source).toMatch(/evidence,\s*\n\s*}/);
225
+ });
226
+
227
+ it('does NOT include traceId field (known inconsistency)', () => {
228
+ // Match the entire data block from painId to the closing brace of the data object.
229
+ // Use a non-greedy dot-all match to avoid truncation at template literal braces like ${Date.now()}
230
+ const llmBlock = source.match(/painId:\s*`llm_\$\{Date\.now\(\)\}`[\s\S]*?^\s{8}\},?/m);
231
+ expect(llmBlock).not.toBeNull();
232
+ expect(llmBlock![0]).not.toMatch(/traceId/);
233
+ });
234
+
235
+ it('sets agentId from ctx.agentId (not hardcoded)', () => {
236
+ expect(source).toMatch(/agentId:\s*ctx\.agentId/);
237
+ });
238
+
239
+ it('uses evaluatePainDiagnosticGate as gate', () => {
240
+ expect(source).toMatch(/evaluatePainDiagnosticGate/);
241
+ });
242
+
243
+ it('gates emit on gate.shouldDiagnose being true', () => {
244
+ expect(source).toMatch(/if\s*\(gate\.shouldDiagnose\)/);
245
+ });
246
+
247
+ it('calls emitPainDetectedEvent WITHOUT await (fire-and-forget)', () => {
248
+ // llm.ts calls emitPainDetectedEvent without await — known inconsistency
249
+ expect(source).toMatch(/[^a]\s*emitPainDetectedEvent\(wctx,/);
250
+ expect(source).not.toMatch(/await\s+emitPainDetectedEvent/);
251
+ });
252
+
253
+ it('uses PEAT-B1 evidence triage (feature-flagged)', () => {
254
+ expect(source).toMatch(/evaluateEvidenceTriage/);
255
+ expect(source).toMatch(/loadFeatureFlagFromConfig/);
256
+ });
257
+ });
258
+
259
+ // ═══════════════════════════════════════════════════════════════════════
260
+ // Section 4: gate-block-helper.ts — gate block persistence
261
+ // ═══════════════════════════════════════════════════════════════════════
262
+
263
+ describe('gate-block-helper.ts emit site', () => {
264
+ const source = read(GATE_BLOCK_HELPER);
265
+
266
+ it('uses painId format: gate_${Date.now()}_${random}', () => {
267
+ expect(source).toMatch(/painId:\s*`gate_\$\{Date\.now\(\)\}_\$\{Math\.random\(\)\.toString\(36\)\.slice\(2,\s*10\)\}`/);
268
+ });
269
+
270
+ it('sets painType to "user_frustration"', () => {
271
+ expect(source).toMatch(/painType:\s*'user_frustration'/);
272
+ });
273
+
274
+ it('sets source to "gate_blocked"', () => {
275
+ expect(source).toMatch(/source:\s*'gate_blocked'/);
276
+ });
277
+
278
+ it('hardcodes agentId to "main"', () => {
279
+ expect(source).toMatch(/agentId:\s*'main'/);
280
+ });
281
+
282
+ it('uses GATE_BLOCK_PAIN_SCORE constant (45)', () => {
283
+ expect(source).toMatch(/GATE_BLOCK_PAIN_SCORE\s*=\s*45/);
284
+ expect(source).toMatch(/score:\s*GATE_BLOCK_PAIN_SCORE/);
285
+ });
286
+
287
+ it('does NOT include provenance field (known inconsistency)', () => {
288
+ const gateBlock = extractGateBlockObject(source);
289
+ expect(gateBlock).not.toBeNull();
290
+ expect(gateBlock).not.toMatch(/provenance/);
291
+ });
292
+
293
+ it('does NOT include evidence field (known inconsistency)', () => {
294
+ const gateBlock = extractGateBlockObject(source);
295
+ expect(gateBlock).not.toBeNull();
296
+ expect(gateBlock).not.toMatch(/evidence/);
297
+ });
298
+
299
+ it('does NOT include traceId field (known inconsistency)', () => {
300
+ const gateBlock = extractGateBlockObject(source);
301
+ expect(gateBlock).not.toBeNull();
302
+ expect(gateBlock).not.toMatch(/traceId/);
303
+ });
304
+
305
+ it('uses evaluatePainDiagnosticGate as gate', () => {
306
+ expect(source).toMatch(/evaluatePainDiagnosticGate/);
307
+ });
308
+
309
+ it('gates emit on gate.shouldDiagnose being true', () => {
310
+ expect(source).toMatch(/if\s*\(gate\.shouldDiagnose\)/);
311
+ });
312
+
313
+ it('uses PEAT-B1 evidence triage (feature-flagged)', () => {
314
+ expect(source).toMatch(/evaluateEvidenceTriage/);
315
+ expect(source).toMatch(/loadFeatureFlagFromConfig/);
316
+ });
317
+
318
+ it('calls emitPainDetectedEvent with void + .catch() (fire-and-forget with error handler)', () => {
319
+ expect(source).toMatch(/void\s+emitPainDetectedEvent/);
320
+ expect(source).toMatch(/\.catch\(\(emitErr\)/);
321
+ });
322
+ });
323
+
324
+ // ═══════════════════════════════════════════════════════════════════════
325
+ // Section 5: Cross-site consistency documentation
326
+ // ═══════════════════════════════════════════════════════════════════════
327
+
328
+ describe('cross-site consistency (documents known inconsistencies)', () => {
329
+ it('all 4 sites call emitPainDetectedEvent (not legacy APIs)', () => {
330
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
331
+ for (const file of files) {
332
+ const src = read(file);
333
+ expect(src).toMatch(/emitPainDetectedEvent/);
334
+ expect(src).not.toMatch(/\bwritePainFlag\b/);
335
+ expect(src).not.toMatch(/\bcreatePainSignalBridge\b/);
336
+ }
337
+ });
338
+
339
+ it('all 4 sites emit type: "pain_detected"', () => {
340
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
341
+ for (const file of files) {
342
+ const src = read(file);
343
+ expect(src).toMatch(/type:\s*'pain_detected'/);
344
+ }
345
+ });
346
+
347
+ it('all 4 sites include ts: new Date().toISOString()', () => {
348
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
349
+ for (const file of files) {
350
+ const src = read(file);
351
+ expect(src).toMatch(/ts:\s*new Date\(\)\.toISOString\(\)/);
352
+ }
353
+ });
354
+
355
+ it('all 4 sites include sessionId field', () => {
356
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
357
+ for (const file of files) {
358
+ const src = read(file);
359
+ expect(src).toMatch(/sessionId,/);
360
+ }
361
+ });
362
+
363
+ it('all 4 sites include score field', () => {
364
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
365
+ for (const file of files) {
366
+ const src = read(file);
367
+ expect(src).toMatch(/score:/);
368
+ }
369
+ });
370
+
371
+ it('all 4 sites include reason field', () => {
372
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
373
+ for (const file of files) {
374
+ const src = read(file);
375
+ expect(src).toMatch(/reason:/);
376
+ }
377
+ });
378
+
379
+ it('all 4 sites include source field', () => {
380
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
381
+ for (const file of files) {
382
+ const src = read(file);
383
+ expect(src).toMatch(/source:/);
384
+ }
385
+ });
386
+
387
+ it('all 4 sites include painId field', () => {
388
+ // after-tool-call-helpers uses shorthand `painId,`; others use `painId:`
389
+ const files = [AFTER_TOOL_CALL_HELPERS, PROMPT, LLM, GATE_BLOCK_HELPER];
390
+ for (const file of files) {
391
+ const src = read(file);
392
+ expect(src).toMatch(/painId[,:]/);
393
+ }
394
+ });
395
+
396
+ it('documents: only after-tool-call-helpers uses evaluateTriggerController', () => {
397
+ const atc = read(AFTER_TOOL_CALL_HELPERS);
398
+ const prompt = read(PROMPT);
399
+ const llm = read(LLM);
400
+ const gate = read(GATE_BLOCK_HELPER);
401
+
402
+ expect(atc).toMatch(/evaluateTriggerController/);
403
+ expect(prompt).not.toMatch(/evaluateTriggerController/);
404
+ expect(llm).not.toMatch(/evaluateTriggerController/);
405
+ expect(gate).not.toMatch(/evaluateTriggerController/);
406
+ });
407
+
408
+ it('documents: 3 of 4 sites use evaluatePainDiagnosticGate (not after-tool-call-helpers)', () => {
409
+ const atc = read(AFTER_TOOL_CALL_HELPERS);
410
+ const prompt = read(PROMPT);
411
+ const llm = read(LLM);
412
+ const gate = read(GATE_BLOCK_HELPER);
413
+
414
+ expect(atc).not.toMatch(/evaluatePainDiagnosticGate/);
415
+ expect(prompt).toMatch(/evaluatePainDiagnosticGate/);
416
+ expect(llm).toMatch(/evaluatePainDiagnosticGate/);
417
+ expect(gate).toMatch(/evaluatePainDiagnosticGate/);
418
+ });
419
+ });
420
+ });