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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/core/code-implementation-storage.ts +0 -31
- package/src/core/principle-tree-ledger.ts +0 -13
- package/src/core/rule-host.ts +77 -153
- package/tests/core/code-implementation-storage.test.ts +16 -114
- package/tests/core/rule-host-sqlite-source.test.ts +720 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +0 -1
- package/tests/hooks/gate-auto-correct.test.ts +0 -1
- package/tests/hooks/gate-no-path-write-tool.test.ts +0 -1
- package/tests/hooks/gate-rule-host-pipeline.test.ts +0 -1
- package/tests/integration/pain-id-chain-e2e.test.ts +59 -5
- package/tests/integration/principle-compiler-e2e.test.ts +62 -13
|
@@ -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.
|
|
@@ -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
|
|
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:
|
|
143
|
-
|
|
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 (
|
|
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(); //
|
|
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
|
-
//
|
|
180
|
-
|
|
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: {
|