@principles/pd-cli 1.151.1 → 1.152.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/commands/console.d.ts.map +1 -1
  2. package/dist/commands/console.js +15 -3
  3. package/dist/commands/console.js.map +1 -1
  4. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  5. package/dist/commands/runtime-internalization-run-rulehost.js +50 -21
  6. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  7. package/dist/services/console-launcher.d.ts +9 -0
  8. package/dist/services/console-launcher.d.ts.map +1 -1
  9. package/dist/services/console-launcher.js +47 -4
  10. package/dist/services/console-launcher.js.map +1 -1
  11. package/dist/services/pd-config-loader.d.ts +5 -0
  12. package/dist/services/pd-config-loader.d.ts.map +1 -1
  13. package/dist/services/pd-config-loader.js +26 -1
  14. package/dist/services/pd-config-loader.js.map +1 -1
  15. package/dist/services/rulehost-pipeline-runner.d.ts +5 -3
  16. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  17. package/dist/services/rulehost-pipeline-runner.js +10 -1
  18. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/commands/console.ts +16 -3
  21. package/src/commands/runtime-internalization-run-rulehost.ts +51 -21
  22. package/src/services/console-launcher.ts +57 -5
  23. package/src/services/pd-config-loader.ts +27 -1
  24. package/src/services/rulehost-pipeline-runner.ts +15 -4
  25. package/tests/commands/console-open.test.ts +59 -1
  26. package/tests/commands/run-rulehost-handler.test.ts +53 -3
  27. package/tests/commands/runtime-activation.test.ts +3 -1
  28. package/tests/e2e/cross-package-acceptance.test.ts +26 -3
  29. package/tests/services/rulehost-pipeline-e2e.test.ts +20 -2
  30. package/tests/services/rulehost-pipeline-runner.test.ts +28 -4
@@ -328,7 +328,9 @@ describe('planConsoleLaunch — reused (healthy console on preferred port)', ()
328
328
  expect(result.reason).toBe('console_authentication_mode_mismatch');
329
329
  // PRI-695: the guidance names the actual running state and both ways
330
330
  // out — it no longer references "Companion" (meaningless to CLI users)
331
- // or PD_CONSOLE_TOKEN (never set by the default install).
331
+ // or PD_CONSOLE_TOKEN (never set by the default install). PRI-784: the
332
+ // no-source form stays source-neutral; the env/flag variants have
333
+ // their own cases below.
332
334
  expect(result.nextAction).toContain('without authentication');
333
335
  expect(result.nextAction).toContain(`port ${addr.port}`);
334
336
  expect(result.nextAction).not.toContain('Companion');
@@ -338,6 +340,56 @@ describe('planConsoleLaunch — reused (healthy console on preferred port)', ()
338
340
  }
339
341
  });
340
342
 
343
+ it('auth-mismatch guidance names PD_CONSOLE_TOKEN and its durable removal when the token came from the env (PRI-784)', async () => {
344
+ const server = http.createServer((_req, res) => {
345
+ res.statusCode = 200;
346
+ res.end(JSON.stringify({ success: true, data: { authenticationMode: 'no_auth' } }));
347
+ });
348
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
349
+ const addr = server.address();
350
+ if (typeof addr !== 'object' || !addr) throw new Error('no addr');
351
+ try {
352
+ const result = await planConsoleLaunch({
353
+ workspaceDir: '/tmp/anywhere', preferredPort: addr.port,
354
+ host: '127.0.0.1', token: 'configured-token', tokenSource: 'env',
355
+ });
356
+ expect(result.status).toBe('refused');
357
+ expect(result.reason).toBe('console_authentication_mode_mismatch');
358
+ // The env source is the recurring real-world trap: a persistently set
359
+ // variable makes "reopen without a token" impossible to follow, so the
360
+ // guidance must name the variable and how to remove it at its source.
361
+ expect(result.nextAction).toContain('PD_CONSOLE_TOKEN');
362
+ expect(result.nextAction).toContain("SetEnvironmentVariable('PD_CONSOLE_TOKEN', $null, 'User')");
363
+ expect(result.nextAction).toContain('--no-auth');
364
+ expect(result.nextAction).toContain('--token');
365
+ } finally {
366
+ await new Promise<void>((resolve) => server.close(resolve));
367
+ }
368
+ });
369
+
370
+ it('auth-mismatch guidance names --token, never the env var, when the token came from the flag (PRI-784)', async () => {
371
+ const server = http.createServer((_req, res) => {
372
+ res.statusCode = 200;
373
+ res.end(JSON.stringify({ success: true, data: { authenticationMode: 'no_auth' } }));
374
+ });
375
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
376
+ const addr = server.address();
377
+ if (typeof addr !== 'object' || !addr) throw new Error('no addr');
378
+ try {
379
+ const result = await planConsoleLaunch({
380
+ workspaceDir: '/tmp/anywhere', preferredPort: addr.port,
381
+ host: '127.0.0.1', token: 'configured-token', tokenSource: 'flag',
382
+ });
383
+ expect(result.status).toBe('refused');
384
+ expect(result.reason).toBe('console_authentication_mode_mismatch');
385
+ expect(result.nextAction).toContain('--token');
386
+ expect(result.nextAction).toContain('--no-auth');
387
+ expect(result.nextAction).not.toContain('PD_CONSOLE_TOKEN');
388
+ } finally {
389
+ await new Promise<void>((resolve) => server.close(resolve));
390
+ }
391
+ });
392
+
341
393
  it('reuses a no-auth Console for a tokenless caller (PRI-695 default post-install state)', async () => {
342
394
  // The installer auto-launches the Console with --no-auth; the documented
343
395
  // reopen command carries no flags and no token. That combination must
@@ -724,6 +776,12 @@ describe('CLI command wiring (pd console open)', () => {
724
776
  expect(run.parsed.status).toBe('refused');
725
777
  expect(run.parsed.reason).toBe('console_authentication_mode_mismatch');
726
778
  expect(run.parsed).not.toHaveProperty('serverPid');
779
+ // PRI-784 review fix (PR #1670): the fresh-start env guidance is part of
780
+ // the user contract — assert its content, not just the refusal shell.
781
+ expect(run.parsed.nextAction).toContain('PD_CONSOLE_TOKEN');
782
+ expect(run.parsed.nextAction).toContain('not forwarded');
783
+ expect(run.parsed.nextAction).toContain('--token "$PD_CONSOLE_TOKEN"');
784
+ expect(run.parsed.nextAction).toContain('--no-auth');
727
785
  await new Promise((resolve) => setTimeout(resolve, 100));
728
786
  expect(await isPortInUse('127.0.0.1', 49391)).toBe(false);
729
787
  } finally {
@@ -573,6 +573,37 @@ function writeContextV2EnabledConfig(workspaceDir: string): void {
573
573
  fs.writeFileSync(path.join(configDir, 'config.yaml'), yaml.dump(cfg), 'utf8');
574
574
  }
575
575
 
576
+ function writeContextV2KillSwitchConfig(workspaceDir: string): void {
577
+ const configDir = path.join(workspaceDir, '.pd');
578
+ fs.mkdirSync(configDir, { recursive: true });
579
+ const cfg = {
580
+ version: 1,
581
+ features: {
582
+ prompt: { category: 'core', enabled: true },
583
+ code_tool_hook: { category: 'core', enabled: true },
584
+ defer_archive: { category: 'core', enabled: true },
585
+ code_rule_capability: { category: 'core', enabled: true },
586
+ // PRI-780: explicit kill switch (the flag itself defaults ON).
587
+ rulecode_context_v2: { category: 'quiet', enabled: false },
588
+ },
589
+ workspace: { default: workspaceDir },
590
+ runtimeProfiles: {
591
+ 'pi-ai.default': { type: 'pi-ai', provider: 'anthropic', model: 'claude-sonnet', apiKeyEnv: 'ANTHROPIC_API_KEY' },
592
+ },
593
+ internalAgents: {
594
+ defaultRuntime: 'pi-ai.default',
595
+ agents: {
596
+ dreamer: { enabled: true, runtimeProfile: 'pi-ai.default' },
597
+ philosopher: { enabled: true, runtimeProfile: 'pi-ai.default' },
598
+ scribe: { enabled: true, runtimeProfile: 'pi-ai.default' },
599
+ artificer: { enabled: true, runtimeProfile: 'pi-ai.default' },
600
+ evaluator: { enabled: true, runtimeProfile: 'pi-ai.default' },
601
+ },
602
+ },
603
+ };
604
+ fs.writeFileSync(path.join(configDir, 'config.yaml'), yaml.dump(cfg), 'utf8');
605
+ }
606
+
576
607
  describe('handleRunRuleHost — PR #1122 behavior-examples fail-fast (CodeRabbit Comment 1+2)', () => {
577
608
  let workspaceDir: string;
578
609
  let savedEnv: NodeJS.ProcessEnv;
@@ -648,8 +679,9 @@ describe('handleRunRuleHost — PR #1122 behavior-examples fail-fast (CodeRabbit
648
679
  it('text dry-run output uses v2-aware capabilityStatus when --behavior-examples is missing (CodeRabbit Comment 2)', async () => {
649
680
  // With contextV2Enabled=true and no --behavior-examples, the handler sets
650
681
  // behaviorExamplesReason='behavior_examples_missing'. The text dry-run
651
- // branch must surface this reason in capabilityStatus (matching the JSON
652
- // branch), not the stale resolvedRuntime.capabilityStatus.
682
+ // branch must derive capabilityStatus from the EFFECTIVE capability
683
+ // (PRI-780 review M2), surfacing the reason — not the stale
684
+ // resolvedRuntime.capabilityStatus ("ON").
653
685
  writeContextV2EnabledConfig(workspaceDir);
654
686
  const { stdout } = await captureStdio(() =>
655
687
  handleRunRuleHost({
@@ -658,6 +690,24 @@ describe('handleRunRuleHost — PR #1122 behavior-examples fail-fast (CodeRabbit
658
690
  workspace: workspaceDir,
659
691
  }),
660
692
  );
661
- expect(stdout).toMatch(/code_rule_capability: OFF \(behavior_examples_missing\)/);
693
+ expect(stdout).toMatch(/code_rule_capability: OFF \(behavior_examples_missing;/);
694
+ expect(stdout).not.toMatch(/code_rule_capability: ON/);
695
+ });
696
+
697
+ it('PRI-780 M2: kill-switch dry-run reports OFF with the kill-switch reason, never ON', async () => {
698
+ writeContextV2KillSwitchConfig(workspaceDir);
699
+ const { stdout } = await captureStdio(() =>
700
+ handleRunRuleHost({
701
+ painId: 'pain-1',
702
+ dryRun: true,
703
+ workspace: workspaceDir,
704
+ }),
705
+ );
706
+ expect(stdout).toMatch(/code_rule_capability: OFF \(rulecode_context_v2_disabled/);
707
+ expect(stdout).not.toMatch(/code_rule_capability: ON/);
708
+ // Codex review round 2 P2: the plain-text "Next" line must derive from the
709
+ // EFFECTIVE capability too — confirmation cannot run the code-rule pipeline.
710
+ expect(stdout).not.toMatch(/Next: pass --confirm to actually run the pipeline/);
711
+ expect(stdout).toMatch(/Next: fix the code-rule capability issue above/);
662
712
  });
663
713
  });
@@ -657,7 +657,9 @@ describe('handleRuntimeActivationList', () => {
657
657
  },
658
658
  ]);
659
659
  // Default mockFeatureFlags has rulecode_context_v2 enabled. Flip it off
660
- // to simulate the production default (the flag is quiet/default-off).
660
+ // to simulate the migration-period kill switch (explicit
661
+ // features.rulecode_context_v2.enabled: false; the registry default is ON
662
+ // since PRI-780).
661
663
  mockFeatureFlags.flags.rulecode_context_v2.enabled = false;
662
664
 
663
665
  await handleRuntimeActivationList({
@@ -171,15 +171,32 @@ function scribeOut(taskId: string, priorId?: string): unknown {
171
171
  };
172
172
  }
173
173
 
174
+ // PRI-780: v2-only generation — the acceptance chain carries an Owner-labelled
175
+ // pack whose cases match artificerV2's goldenTraceCases (parity is checked).
176
+ const XPKG_RULE_CONTEXT = {
177
+ version: 2 as const,
178
+ history: { status: 'available' as const, truncated: false, calls: [] },
179
+ facts: { priorReadOfTarget: 'unknown' as const, readCount: 0, writeCount: 0, uniqueWritePathCount: 0, sameActionBlockCount: null },
180
+ };
181
+ const XPKG_TEST_PACK = {
182
+ sourceNegativeCase: { caseId: 'neg-1', kind: 'negative' as const, toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' as const, ruleContext: XPKG_RULE_CONTEXT },
183
+ ownerDesiredOutcome: 'block system path writes',
184
+ positiveCounterexamples: [{ caseId: 'pos-1', kind: 'positive' as const, toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow' as const, ruleContext: XPKG_RULE_CONTEXT }],
185
+ evidenceRefs: ['pain://xpkg-1'],
186
+ redactionNotes: [],
187
+ };
188
+
174
189
  function artificerV2(taskId: string, priorId?: string): unknown {
175
190
  return {
176
191
  taskId, sourceScribeArtifactId: requireLineage(priorId, 'sourceScribeArtifactId'),
192
+ requiresContextVersion: 2,
193
+ evidenceRefs: ['pain://xpkg-1'],
177
194
  implementationPlan: { summary: 'Block /etc writes', targetSurface: 'rule-host', changes: ['matcher'], tests: ['unit'], rolloutNotes: ['shadow'], confidence: 0.85 },
178
195
  implementationCode: 'function evaluate(input, helpers) { const p = String(input?.action?.paramsSummary?.path ?? input?.action?.normalizedPath ?? ""); if (p.startsWith("/etc")) return { decision: "block", matched: true, reason: "system path" }; const ctx = input?.context; if (ctx && ctx.facts && ctx.facts.priorReadOfTarget === "no") { return { decision: "block", matched: true, reason: "no prior read" }; } return { decision: "allow", matched: false, reason: "ok" }; }',
179
196
  implementationSummary: 'Block system path writes',
180
197
  goldenTraceCases: [
181
- { caseId: 'pos-1', kind: 'positive', toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow' },
182
- { caseId: 'neg-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' },
198
+ { caseId: 'pos-1', kind: 'positive', toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow', ruleContext: XPKG_RULE_CONTEXT },
199
+ { caseId: 'neg-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block', ruleContext: XPKG_RULE_CONTEXT },
183
200
  ],
184
201
  affectedTools: ['write_file'],
185
202
  sourceTrace: { scribeArtifactId: requireLineage(priorId, 'sourceTrace.scribeArtifactId') },
@@ -267,6 +284,7 @@ describe('Cross-Package Acceptance Test (PRI-408 P1/P2 fixes) — unsplippable c
267
284
  pollIntervalMs: 5,
268
285
  timeoutMs: 5000,
269
286
  codeRuleCapability: capability,
287
+ behaviorExamplePack: XPKG_TEST_PACK,
270
288
  onStoreReady: (store) => { adapter.artifactStore = store; },
271
289
  });
272
290
 
@@ -377,7 +395,9 @@ describe('Cross-Package Acceptance Test (PRI-408 P1/P2 fixes) — unsplippable c
377
395
  {
378
396
  writers: [
379
397
  new PromptWriter(),
380
- new RuleHostWriter({ gateDeps: createProductionGateDeps() }),
398
+ // PRI-780: rulecode_context_v2 defaults ON — production callers
399
+ // inject the real flag probe; mirror that here for the v2 artifact.
400
+ new RuleHostWriter({ gateDeps: createProductionGateDeps(), featureFlagProbe: (flagId) => flagId === 'rulecode_context_v2' }),
381
401
  new DeferArchiveWriter(),
382
402
  ],
383
403
  approvalQueueStore: approvalStore,
@@ -426,12 +446,15 @@ describe('Cross-Package Acceptance Test (PRI-408 P1/P2 fixes) — unsplippable c
426
446
  // Shadow mode is observation-only. The RuleHost loads the activation but
427
447
  // records shadowDecisions instead of blocking. evaluate() must return
428
448
  // undefined (no block) for shadow activations, even for /etc/passwd.
449
+ // PRI-780: production RuleHostInput always carries context (flag on) —
450
+ // the v2 activation only loads when context.version === 2.
429
451
  const makeRuleHostInput = (targetPath: string): RuleHostInput => ({
430
452
  action: { toolName: 'write_file', normalizedPath: targetPath, paramsSummary: { path: targetPath } },
431
453
  workspace: { isRiskPath: targetPath.startsWith('/etc') },
432
454
  session: { currentGfi: 0 },
433
455
  evolution: { epTier: 0 },
434
456
  derived: { estimatedLineChanges: 1, bashRisk: 'safe' },
457
+ context: XPKG_RULE_CONTEXT,
435
458
  });
436
459
  const shadowRuleHost = new RuleHost(
437
460
  path.join(tmpDir, '.state'),
@@ -168,6 +168,15 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
168
168
  const dir = mkTmpDir();
169
169
  tmpDirs.push(dir);
170
170
  writeConfig(dir, configFactory(dir));
171
+ // PRI-780: v2-only generation — every dry-run in this suite carries a
172
+ // valid Owner-labelled behavior-examples file so the capability assertions
173
+ // isolate their intended variable (agent/flag/key state), not the
174
+ // behavior_examples_missing refusal.
175
+ fs.writeFileSync(
176
+ path.join(dir, 'behavior-examples.json'),
177
+ JSON.stringify({ ownerDesiredOutcome: 'block system path writes', sourceNegativeToolCallId: 1, positiveToolCallIds: [2] }),
178
+ 'utf8',
179
+ );
171
180
  return dir;
172
181
  }
173
182
 
@@ -188,6 +197,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
188
197
  await handleRunRuleHost({
189
198
  workspace,
190
199
  painId: 'pain-wiring-001',
200
+ behaviorExamples: 'behavior-examples.json',
191
201
  dryRun: true,
192
202
  json: true,
193
203
  });
@@ -212,7 +222,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
212
222
  return config;
213
223
  });
214
224
 
215
- await handleRunRuleHost({ workspace, painId: 'pain-flag-default', dryRun: true, json: true });
225
+ await handleRunRuleHost({ workspace, painId: 'pain-flag-default', behaviorExamples: 'behavior-examples.json', dryRun: true, json: true });
216
226
 
217
227
  const output = parseJsonOutput();
218
228
  const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
@@ -235,7 +245,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
235
245
  return config;
236
246
  });
237
247
 
238
- await handleRunRuleHost({ workspace, painId: 'pain-emergency-disable', dryRun: true, json: true });
248
+ await handleRunRuleHost({ workspace, painId: 'pain-emergency-disable', behaviorExamples: 'behavior-examples.json', dryRun: true, json: true });
239
249
 
240
250
  const output = parseJsonOutput();
241
251
  const codeRuleCapability = requireRecord(output.codeRuleCapability, 'codeRuleCapability');
@@ -316,6 +326,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
316
326
  await handleRunRuleHost({
317
327
  workspace,
318
328
  painId: 'pain-wiring-002',
329
+ behaviorExamples: 'behavior-examples.json',
319
330
  dryRun: true,
320
331
  json: true,
321
332
  });
@@ -336,6 +347,7 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
336
347
  await handleRunRuleHost({
337
348
  workspace,
338
349
  painId: 'pain-wiring-003',
350
+ behaviorExamples: 'behavior-examples.json',
339
351
  dryRun: true,
340
352
  json: true,
341
353
  });
@@ -397,10 +409,16 @@ describe('runRuleHost production-wiring (PRI-429) — deterministic, no LLM', ()
397
409
  },
398
410
  },
399
411
  });
412
+ fs.writeFileSync(
413
+ path.join(dir, 'behavior-examples.json'),
414
+ JSON.stringify({ ownerDesiredOutcome: 'block system path writes', sourceNegativeToolCallId: 1, positiveToolCallIds: [2] }),
415
+ 'utf8',
416
+ );
400
417
 
401
418
  await handleRunRuleHost({
402
419
  workspace: dir,
403
420
  painId: 'pain-wiring-004',
421
+ behaviorExamples: 'behavior-examples.json',
404
422
  dryRun: true,
405
423
  json: true,
406
424
  });
@@ -134,16 +134,33 @@ function scribeOut(taskId: string, priorId?: string): unknown {
134
134
  };
135
135
  }
136
136
 
137
+ // PRI-780: v2-only generation — the pipeline requires an Owner-labelled pack
138
+ // and the artificer output must satisfy the v2 contract (case parity incl.
139
+ // ruleContext + verbatim evidenceRefs).
140
+ const PIPE_RULE_CONTEXT = {
141
+ version: 2 as const,
142
+ history: { status: 'available' as const, truncated: false, calls: [] },
143
+ facts: { priorReadOfTarget: 'unknown' as const, readCount: 0, writeCount: 0, uniqueWritePathCount: 0, sameActionBlockCount: null },
144
+ };
145
+ const PIPE_POS_CASE = { caseId: 'pos-1', kind: 'positive' as const, toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow' as const, ruleContext: PIPE_RULE_CONTEXT };
146
+ const PIPE_NEG_CASE = { caseId: 'neg-1', kind: 'negative' as const, toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' as const, ruleContext: PIPE_RULE_CONTEXT };
147
+ const PIPE_PACK = {
148
+ sourceNegativeCase: PIPE_NEG_CASE,
149
+ ownerDesiredOutcome: 'block system path writes',
150
+ positiveCounterexamples: [PIPE_POS_CASE],
151
+ evidenceRefs: ['pain://pipe-1'],
152
+ redactionNotes: [],
153
+ };
154
+
137
155
  function artificerV2(taskId: string, priorId?: string): unknown {
138
156
  return {
139
157
  taskId, sourceScribeArtifactId: requireLineage(priorId, 'sourceScribeArtifactId'),
158
+ requiresContextVersion: 2,
159
+ evidenceRefs: ['pain://pipe-1'],
140
160
  implementationPlan: { summary: 'Block /etc writes', targetSurface: 'rule-host', changes: ['matcher'], tests: ['unit'], rolloutNotes: ['shadow'], confidence: 0.85 },
141
161
  implementationCode: 'function evaluate(input, helpers) { const p = String(input?.action?.paramsSummary?.path ?? input?.action?.normalizedPath ?? ""); if (p.startsWith("/etc")) return { decision: "block", matched: true, reason: "system path" }; const ctx = input?.context; if (ctx && ctx.facts && ctx.facts.priorReadOfTarget === "no") { return { decision: "block", matched: true, reason: "no prior read" }; } return { decision: "allow", matched: false, reason: "ok" }; }',
142
162
  implementationSummary: 'Block system path writes',
143
- goldenTraceCases: [
144
- { caseId: 'pos-1', kind: 'positive', toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow' },
145
- { caseId: 'neg-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' },
146
- ],
163
+ goldenTraceCases: [PIPE_POS_CASE, PIPE_NEG_CASE],
147
164
  affectedTools: ['write_file'],
148
165
  sourceTrace: { scribeArtifactId: requireLineage(priorId, 'sourceTrace.scribeArtifactId') },
149
166
  risks: [], generatedAt: new Date().toISOString(),
@@ -266,6 +283,7 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
266
283
  workspaceDir: tmpDir, painId: 'pain-test-001', runtimeAdapter: adapter,
267
284
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
268
285
  codeRuleCapability: capability,
286
+ behaviorExamplePack: PIPE_PACK,
269
287
  onStoreReady: (store) => { adapter.artifactStore = store; },
270
288
  });
271
289
 
@@ -320,6 +338,7 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
320
338
  painId: 'pain-feedback-001',
321
339
  runtimeAdapter: adapter,
322
340
  codeRuleCapability: { enabled: true, artificerAdapter: adapter },
341
+ behaviorExamplePack: PIPE_PACK,
323
342
  channel: 'code_tool_hook',
324
343
  pollIntervalMs: 5,
325
344
  timeoutMs: 1000,
@@ -349,6 +368,7 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
349
368
  workspaceDir: tmpDir, painId: 'pain-test-001', runtimeAdapter: adapter,
350
369
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
351
370
  codeRuleCapability: capability,
371
+ behaviorExamplePack: PIPE_PACK,
352
372
  onStoreReady: (store) => { adapter.artifactStore = store; },
353
373
  });
354
374
 
@@ -376,6 +396,7 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
376
396
  workspaceDir: tmpDir, painId: 'pain-test-001', runtimeAdapter: adapter,
377
397
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
378
398
  codeRuleCapability: capability,
399
+ behaviorExamplePack: PIPE_PACK,
379
400
  onStoreReady: (store) => { adapter.artifactStore = store; },
380
401
  });
381
402
 
@@ -595,6 +616,7 @@ describe('runRuleHostPipeline (PRI-429) — atomic capability + exact pain match
595
616
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
596
617
  maxStageRetries: 2,
597
618
  codeRuleCapability: capability,
619
+ behaviorExamplePack: PIPE_PACK,
598
620
  onStoreReady: (store) => { adapter.artifactStore = store; },
599
621
  });
600
622
 
@@ -725,6 +747,7 @@ describe('runRuleHostPipeline — outputLanguage reaches adapter.startRun messag
725
747
  workspaceDir: tmpDir, painId: 'pain-lang-en', runtimeAdapter: adapter,
726
748
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
727
749
  codeRuleCapability: { enabled: true, artificerAdapter: adapter },
750
+ behaviorExamplePack: PIPE_PACK,
728
751
  onStoreReady: (store) => { adapter.artifactStore = store; },
729
752
  });
730
753
  expect(result.decision, JSON.stringify(result)).toBe('candidate_ready_for_owner_review');
@@ -771,6 +794,7 @@ describe('runRuleHostPipeline — outputLanguage reaches adapter.startRun messag
771
794
  workspaceDir: tmpDir, painId: 'pain-lang-zh', runtimeAdapter: adapter,
772
795
  channel: 'code_tool_hook', pollIntervalMs: 5, timeoutMs: 1000,
773
796
  codeRuleCapability: { enabled: true, artificerAdapter: adapter },
797
+ behaviorExamplePack: PIPE_PACK,
774
798
  onStoreReady: (store) => { adapter.artifactStore = store; },
775
799
  });
776
800
  expect(result.decision, JSON.stringify(result)).toBe('candidate_ready_for_owner_review');