principles-disciple 1.79.0 → 1.81.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.
@@ -1,173 +1,50 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as os from 'os';
4
- import * as path from 'path';
5
- import { WorkspaceContext } from '../../src/core/workspace-context.js';
6
- import { ConfigService } from '../../src/core/config-service.js';
7
-
8
- // Shared mocks
9
- const mockLearner = {
10
- getStore: vi.fn(() => ({ keywords: [{ term: 'wrong', weight: 0.5 }] })),
11
- };
12
-
13
- const mockDb = {
14
- listRecentSessions: vi.fn(() => [{ sessionId: 'session-1' }]),
15
- listUserTurnsForSession: vi.fn(() => [{ rawExcerpt: 'User said wrong input', correctionDetected: true, correctionCue: 'wrong' }]),
16
- };
17
-
18
- const mockOptimizationService = {
19
- buildTrajectoryHistory: vi.fn(async () => [
20
- { sessionId: 'session-1', timestamp: 'now', term: 'wrong', userMessage: '' }
21
- ]),
22
- applyResult: vi.fn(),
23
- };
24
-
25
- // Mock dependencies
26
- vi.mock('../../src/core/correction-cue-learner.js', () => ({
27
- CorrectionCueLearner: { get: vi.fn(() => mockLearner) },
28
- }));
29
-
30
- vi.mock('../../src/core/trajectory.js', () => ({
31
- TrajectoryRegistry: {
32
- get: vi.fn(() => mockDb),
33
- clear: vi.fn(),
34
- },
35
- }));
36
-
37
- vi.mock('../../src/service/keyword-optimization-service.js', () => ({
38
- KeywordOptimizationService: { get: vi.fn(() => mockOptimizationService) },
39
- }));
40
-
41
- // Mock principles-core runtime-v2 observer/scheduler classes
42
- const mockDispatch = vi.fn().mockResolvedValue({
43
- updated: true,
44
- summary: 'Keyword store optimized',
45
- updates: { wrong: { action: 'update', weight: 0.4, reasoning: 'slightly high FP' } }
46
- });
47
-
48
- const mockRegister = vi.fn();
49
-
50
- vi.mock('@principles/core/runtime-v2', () => {
51
- return {
52
- WorkflowFunnelLoader: class {
53
- getFunnel = vi.fn(() => ({
54
- policy: {
55
- runtimeKind: 'pi-ai',
56
- provider: 'anthropic',
57
- model: 'anthropic/claude-3-5-sonnet',
58
- apiKeyEnv: 'ANTHROPIC_API_KEY',
59
- timeoutMs: 30000,
60
- }
61
- }));
62
- },
63
- PiAiRuntimeAdapter: class {},
64
- CorrectionObserver: class {},
65
- AgentScheduler: class {
66
- register = mockRegister;
67
- dispatch = mockDispatch;
68
- }
69
- };
70
- });
71
-
72
- // Import EvolutionWorkerService
73
- import { EvolutionWorkerService } from '../../src/service/evolution-worker.js';
74
- import { safeRmDir } from '../test-utils.js';
75
-
76
- function createMockApi() {
77
- return {
78
- logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
79
- runtime: {
80
- agent: { runEmbeddedPiAgent: vi.fn() },
81
- system: {
82
- requestHeartbeatNow: vi.fn(),
83
- runHeartbeatOnce: vi.fn(),
84
- },
85
- },
86
- } as any;
87
- }
88
-
89
- const fastPollConfig = { get: (k: string) => k === 'intervals.worker_poll_ms' ? 1000 : undefined };
90
-
91
- describe('EvolutionWorkerService Correction Observer Integration', () => {
92
- beforeEach(() => {
93
- vi.useFakeTimers();
94
- vi.clearAllMocks();
95
- EvolutionWorkerService.api = null;
96
- });
97
-
98
- afterEach(() => {
99
- vi.useRealTimers();
100
- EvolutionWorkerService.api = null;
1
+ import { describe, expect, it } from 'vitest';
2
+ import { PLUGIN_SURFACE_REGISTRY } from '@principles/core/runtime-v2';
3
+ import { DEFAULT_FEATURE_FLAGS, computeEffectiveFlags } from '@principles/core/runtime-v2';
4
+
5
+ describe('Correction Observer Ownership Feature Flag & Surface Registry Consistency (PRI-293, ERR-027)', () => {
6
+ it('correction_observer feature flag is registered as quiet with enabled:true (disableable runtime kill switch)', () => {
7
+ const flag = DEFAULT_FEATURE_FLAGS.find(f => f.id === 'correction_observer');
8
+ expect(flag).toBeDefined();
9
+ expect(flag!.category).toBe('quiet');
10
+ expect(flag!.enabled).toBe(true);
101
11
  });
102
12
 
103
- it('runs Correction Observer on heartbeat and applies updates when configured', async () => {
104
- const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-worker-corr-'));
105
- const stateDir = path.join(workspaceDir, '.state');
106
- fs.mkdirSync(stateDir, { recursive: true });
107
-
108
- // Initialize an empty queue to avoid processing actual queue items
109
- fs.writeFileSync(
110
- path.join(stateDir, 'evolution_queue.json'),
111
- JSON.stringify([], null, 2),
112
- 'utf8'
13
+ it('correction_observer can be disabled via workspace config (P1 fix — runtime kill switch)', () => {
14
+ const result = computeEffectiveFlags(
15
+ { correction_observer: { enabled: false } },
16
+ DEFAULT_FEATURE_FLAGS,
17
+ '.pd/feature-flags.yaml',
113
18
  );
19
+ expect(result.flags['correction_observer'].enabled).toBe(false);
20
+ expect(result.warnings).not.toContain(expect.stringContaining('core flag cannot be disabled'));
21
+ });
114
22
 
115
- // Initialize pain settings with fast poll interval
116
- fs.writeFileSync(
117
- path.join(stateDir, 'pain_settings.json'),
118
- JSON.stringify({
119
- intervals: {
120
- worker_poll_ms: 1000
121
- }
122
- }, null, 2),
123
- 'utf8'
124
- );
125
-
126
- // Invalidate workspace cache to load the newly written settings
127
- WorkspaceContext.clearCache();
128
- ConfigService.reset();
129
-
130
- const mockApi = createMockApi();
131
- EvolutionWorkerService.api = mockApi;
132
-
133
- try {
134
- EvolutionWorkerService.start({
135
- workspaceDir,
136
- stateDir,
137
- logger: mockApi.logger,
138
- config: fastPollConfig,
139
- api: mockApi,
140
- } as any);
141
-
142
- // 1. Advance to startup timer (5000ms) and wait for microtasks to settle
143
- await vi.advanceTimersByTimeAsync(5000);
144
- for (let i = 0; i < 20; i++) {
145
- await Promise.resolve();
146
- }
147
-
148
- // 2. Advance past the poll interval (1000ms) to trigger runCycle
149
- await vi.advanceTimersByTimeAsync(1050);
150
- for (let i = 0; i < 20; i++) {
151
- await Promise.resolve();
152
- }
23
+ it('service:correction-observer surface is registered as core with enabledByDefault:true', () => {
24
+ const surface = PLUGIN_SURFACE_REGISTRY.find(s => s.id === 'service:correction-observer');
25
+ expect(surface).toBeDefined();
26
+ expect(surface!.category).toBe('core');
27
+ expect(surface!.enabledByDefault).toBe(true);
28
+ });
153
29
 
154
- // Verify that the scheduler dispatch was called with correct payload
155
- expect(mockRegister).toHaveBeenCalled();
156
- expect(mockDispatch).toHaveBeenCalledWith('correction-observer', expect.objectContaining({
157
- parentSessionId: 'evolution-worker',
158
- workspaceDir,
159
- recentMessages: ['User said wrong input'],
160
- }));
30
+ it('startup:correction-observer surface is registered as core with enabledByDefault:true', () => {
31
+ const surface = PLUGIN_SURFACE_REGISTRY.find(s => s.id === 'startup:correction-observer');
32
+ expect(surface).toBeDefined();
33
+ expect(surface!.category).toBe('core');
34
+ expect(surface!.enabledByDefault).toBe(true);
35
+ });
161
36
 
162
- // Verify updates were applied using KeywordOptimizationService
163
- expect(mockOptimizationService.applyResult).toHaveBeenCalledWith(expect.objectContaining({
164
- updated: true,
165
- summary: 'Keyword store optimized',
166
- }));
37
+ it('evolution_worker feature flag remains quiet with enabled:false', () => {
38
+ const flag = DEFAULT_FEATURE_FLAGS.find(f => f.id === 'evolution_worker');
39
+ expect(flag).toBeDefined();
40
+ expect(flag!.category).toBe('quiet');
41
+ expect(flag!.enabled).toBe(false);
42
+ });
167
43
 
168
- } finally {
169
- EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
170
- safeRmDir(workspaceDir);
171
- }
44
+ it('service:evolution-worker surface remains quiet with enabledByDefault:false', () => {
45
+ const surface = PLUGIN_SURFACE_REGISTRY.find(s => s.id === 'service:evolution-worker');
46
+ expect(surface).toBeDefined();
47
+ expect(surface!.category).toBe('quiet');
48
+ expect(surface!.enabledByDefault).toBe(false);
172
49
  });
173
50
  });