principles-disciple 1.123.0 → 1.124.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.
@@ -52,7 +52,6 @@ vi.mock('../../src/core/rule-host.js', () => ({
52
52
 
53
53
  vi.mock('../../src/core/principle-tree-ledger.js', () => ({
54
54
  loadLedger: vi.fn(),
55
- listImplementationsByLifecycleState: vi.fn(() => []),
56
55
  }));
57
56
 
58
57
  vi.mock('../../src/hooks/gate-block-helper.js', () => ({
@@ -55,7 +55,6 @@ vi.mock('../../src/core/rule-host.js', () => ({
55
55
 
56
56
  vi.mock('../../src/core/principle-tree-ledger.js', () => ({
57
57
  loadLedger: vi.fn(),
58
- listImplementationsByLifecycleState: vi.fn(() => []),
59
58
  }));
60
59
 
61
60
  vi.mock('../../src/hooks/gate-block-helper.js', () => ({
@@ -54,7 +54,6 @@ vi.mock('../../src/core/rule-host.js', () => ({
54
54
 
55
55
  vi.mock('../../src/core/principle-tree-ledger.js', () => ({
56
56
  loadLedger: vi.fn(),
57
- listImplementationsByLifecycleState: vi.fn(() => []),
58
57
  }));
59
58
 
60
59
  // Mock WorkspaceContext to return a controlled instance with our mockEventLog.
@@ -53,7 +53,6 @@ vi.mock('../../src/core/rule-host.js', () => ({
53
53
 
54
54
  vi.mock('../../src/core/principle-tree-ledger.js', () => ({
55
55
  loadLedger: vi.fn(),
56
- listImplementationsByLifecycleState: vi.fn(() => []),
57
56
  }));
58
57
 
59
58
  describe('Gate Rule Host Only Pipeline', () => {
@@ -26,8 +26,8 @@ import { RuleHost } from '../../src/core/rule-host.js';
26
26
  import { EvolutionReducerImpl } from '../../src/core/evolution-reducer.js';
27
27
  import {
28
28
  loadLedger,
29
- transitionImplementationState,
30
29
  } from '../../src/core/principle-tree-ledger.js';
30
+ import { SqliteConnection, SqliteActivationStateStore } from '@principles/core/runtime-v2';
31
31
  import { safeRmDir } from '../test-utils.js';
32
32
  import type { RuleHostInput } from '../../src/core/rule-host-types.js';
33
33
 
@@ -58,6 +58,60 @@ function disposeTestWorkspace(ws: TestWorkspace): void {
58
58
  safeRmDir(ws.workspaceDir);
59
59
  }
60
60
 
61
+ /**
62
+ * PRI-436: Insert compiled rule code as a pi_artifacts row + activations row
63
+ * so RuleHost can load it from SQLite (the sole production source).
64
+ */
65
+ function activateCompiledRule(
66
+ workspaceDir: string,
67
+ ruleId: string,
68
+ implementationCode: string,
69
+ principleId: string,
70
+ ): void {
71
+ const sqliteConn = new SqliteConnection(workspaceDir);
72
+ try {
73
+ const db = sqliteConn.getDb();
74
+ const now = new Date().toISOString();
75
+ const artifactId = `art-${ruleId}`;
76
+
77
+ const contentJson = JSON.stringify({
78
+ principleId,
79
+ ruleId,
80
+ implementationCode,
81
+ });
82
+
83
+ db.prepare(`
84
+ INSERT INTO pi_artifacts (artifact_id, artifact_kind, source_task_id, source_principle_id, source_rule_id, lineage_artifact_ids, validation_status, content_json, created_at, updated_at)
85
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
86
+ `).run(
87
+ artifactId,
88
+ 'rule',
89
+ `task-${ruleId}`,
90
+ principleId,
91
+ ruleId,
92
+ '[]',
93
+ 'validated',
94
+ contentJson,
95
+ now,
96
+ now,
97
+ );
98
+
99
+ const store = new SqliteActivationStateStore(sqliteConn);
100
+ store.recordActivation({
101
+ activationId: `act_code_${ruleId}`,
102
+ idempotencyKey: `${artifactId}::code_tool_hook`,
103
+ artifactId,
104
+ channel: 'code_tool_hook',
105
+ action: 'code_tool_hook_shadow_activate',
106
+ targetRef: `impl://${ruleId}`,
107
+ activatedAt: now,
108
+ deactivatedAt: null,
109
+ });
110
+ } finally {
111
+ try { sqliteConn.close(); } catch { /* best-effort */ }
112
+ }
113
+ }
114
+
61
115
  // ---------------------------------------------------------------------------
62
116
  // Tests
63
117
  // ---------------------------------------------------------------------------
@@ -134,16 +188,16 @@ describe('Pain ID Chain E2E: pain event → principle → compile → RuleHost',
134
188
  expect(compileResult.ruleId).toBeDefined();
135
189
  expect(compileResult.implementationId).toBeDefined();
136
190
 
137
- // Verify implementation is candidate (not active — must be promoted before enforcing)
191
+ // Verify implementation is candidate (not active — must be activated before enforcing)
138
192
  const updatedLedger = loadLedger(ws.stateDir);
139
193
  const impl = updatedLedger.tree.implementations[compileResult.implementationId!];
140
194
  expect(impl.lifecycleState).toBe('candidate');
141
195
 
142
- // ── Step 5: Promote to active so RuleHost will enforce ──
143
- transitionImplementationState(ws.stateDir, compileResult.implementationId!, 'active');
196
+ // ── Step 5: Activate via SQLite (PRI-436: sole production source) ──
197
+ activateCompiledRule(ws.workspaceDir, compileResult.ruleId!, compileResult.code!, principleId!);
144
198
 
145
199
  // ── Step 6: RuleHost.evaluate(matching input) → block ──
146
- const host = new RuleHost(ws.stateDir, { warn: () => {} });
200
+ const host = new RuleHost(ws.stateDir, { warn: () => {} }, { workspaceDir: ws.workspaceDir });
147
201
 
148
202
  const matchingInput: RuleHostInput = {
149
203
  action: {
@@ -21,8 +21,8 @@ import { RuleHost } from '../../src/core/rule-host.js';
21
21
  import {
22
22
  loadLedger,
23
23
  saveLedger,
24
- transitionImplementationState,
25
24
  } from '../../src/core/principle-tree-ledger.js';
25
+ import { SqliteConnection, SqliteActivationStateStore } from '@principles/core/runtime-v2';
26
26
  import type { RuleHostInput } from '../../src/core/rule-host-types.js';
27
27
 
28
28
  // ---------------------------------------------------------------------------
@@ -50,6 +50,60 @@ function disposeTestWorkspace(ws: TestWorkspace): void {
50
50
  fs.rmSync(ws.workspaceDir, { recursive: true, force: true });
51
51
  }
52
52
 
53
+ /**
54
+ * PRI-436: Insert compiled rule code as a pi_artifacts row + activations row
55
+ * so RuleHost can load it from SQLite (the sole production source).
56
+ */
57
+ function activateCompiledRule(
58
+ workspaceDir: string,
59
+ ruleId: string,
60
+ implementationCode: string,
61
+ principleId: string,
62
+ ): void {
63
+ const sqliteConn = new SqliteConnection(workspaceDir);
64
+ try {
65
+ const db = sqliteConn.getDb();
66
+ const now = new Date().toISOString();
67
+ const artifactId = `art-${ruleId}`;
68
+
69
+ const contentJson = JSON.stringify({
70
+ principleId,
71
+ ruleId,
72
+ implementationCode,
73
+ });
74
+
75
+ db.prepare(`
76
+ INSERT INTO pi_artifacts (artifact_id, artifact_kind, source_task_id, source_principle_id, source_rule_id, lineage_artifact_ids, validation_status, content_json, created_at, updated_at)
77
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
78
+ `).run(
79
+ artifactId,
80
+ 'rule',
81
+ `task-${ruleId}`,
82
+ principleId,
83
+ ruleId,
84
+ '[]',
85
+ 'validated',
86
+ contentJson,
87
+ now,
88
+ now,
89
+ );
90
+
91
+ const store = new SqliteActivationStateStore(sqliteConn);
92
+ store.recordActivation({
93
+ activationId: `act_code_${ruleId}`,
94
+ idempotencyKey: `${artifactId}::code_tool_hook`,
95
+ artifactId,
96
+ channel: 'code_tool_hook',
97
+ action: 'code_tool_hook_shadow_activate',
98
+ targetRef: `impl://${ruleId}`,
99
+ activatedAt: now,
100
+ deactivatedAt: null,
101
+ });
102
+ } finally {
103
+ try { sqliteConn.close(); } catch { /* best-effort */ }
104
+ }
105
+ }
106
+
53
107
  // ---------------------------------------------------------------------------
54
108
  // Tests
55
109
  // ---------------------------------------------------------------------------
@@ -168,21 +222,16 @@ describe('Principle Compiler E2E: compile → promote → RuleHost blocks', () =
168
222
  },
169
223
  };
170
224
 
171
- // ── Step 4: RuleHost should NOT block yet (candidate not loaded) ──
172
- const hostBeforePromote = new RuleHost(ws.stateDir, { warn: () => {} });
225
+ // ── Step 4: RuleHost should NOT block yet (no SQLite activation yet) ──
226
+ const hostBeforePromote = new RuleHost(ws.stateDir, { warn: () => {} }, { workspaceDir: ws.workspaceDir });
173
227
  const noBlockResult = hostBeforePromote.evaluate(matchingInput);
174
- expect(noBlockResult).toBeUndefined(); // candidate not loaded → no block
175
-
176
- // ── Step 5: Promote to 'active' so RuleHost will enforce ──
177
- transitionImplementationState(ws.stateDir, implId, 'active');
228
+ expect(noBlockResult).toBeUndefined(); // no activation → no block
178
229
 
179
- // Verify promotion
180
- const ledgerAfterPromote = loadLedger(ws.stateDir);
181
- const implAfterPromote = ledgerAfterPromote.tree.implementations[implId];
182
- expect(implAfterPromote.lifecycleState).toBe('active');
230
+ // ── Step 5: Activate via SQLite (PRI-436: sole production source) ──
231
+ activateCompiledRule(ws.workspaceDir, result.ruleId!, result.code!, principleId);
183
232
 
184
233
  // ── Step 6: Create RuleHost and evaluate with matching input ──
185
- const host = new RuleHost(ws.stateDir, { warn: () => {} });
234
+ const host = new RuleHost(ws.stateDir, { warn: () => {} }, { workspaceDir: ws.workspaceDir });
186
235
 
187
236
  // Matching input: bash tool with a heartbeat command (defined in Step 4)
188
237
  const blockResult = host.evaluate(matchingInput);
@@ -224,7 +273,7 @@ describe('Principle Compiler E2E: compile → promote → RuleHost blocks', () =
224
273
  });
225
274
 
226
275
  it('should return undefined when no active implementations exist', () => {
227
- const host = new RuleHost(ws.stateDir, { warn: () => {} });
276
+ const host = new RuleHost(ws.stateDir, { warn: () => {} }, { workspaceDir: ws.workspaceDir });
228
277
 
229
278
  const input: RuleHostInput = {
230
279
  action: {