principles-disciple 1.11.0 → 1.13.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.
Files changed (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Rule Host Types — Execution contracts for hosted code implementations
3
+ *
4
+ * PURPOSE: Define the constrained interface through which active code
5
+ * implementations are executed. Implementations receive a frozen snapshot
6
+ * of context and return one of three decisions.
7
+ *
8
+ * TRUST BOUNDARY:
9
+ * - RuleHostInput is a frozen snapshot — no live workspace handles
10
+ * - Implementations execute in a constrained vm context with minimal helpers
11
+ * - No filesystem, process, require, dynamic import, eval, or network access
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Input: Frozen snapshot provided to implementations
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface RuleHostInput {
19
+ action: {
20
+ toolName: string;
21
+ normalizedPath: string | null;
22
+ paramsSummary: Record<string, unknown>;
23
+ };
24
+ workspace: {
25
+ isRiskPath: boolean;
26
+ planStatus: 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN';
27
+ hasPlanFile: boolean;
28
+ };
29
+ session: {
30
+ sessionId?: string;
31
+ currentGfi: number;
32
+ recentThinking: boolean;
33
+ };
34
+ evolution: {
35
+ epTier: number;
36
+ };
37
+ derived: {
38
+ estimatedLineChanges: number;
39
+ bashRisk: 'safe' | 'normal' | 'dangerous' | 'unknown';
40
+ };
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Decision: Limited to three outcomes
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export type RuleHostDecision = 'allow' | 'block' | 'requireApproval';
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Meta: Exported by each implementation for identification
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export interface RuleHostMeta {
54
+ name: string;
55
+ version: string;
56
+ ruleId: string;
57
+ coversCondition: string;
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Result: Structured output from a single implementation evaluation
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export interface RuleHostResult {
65
+ decision: RuleHostDecision;
66
+ matched: boolean;
67
+ reason: string;
68
+ diagnostics?: Record<string, unknown>;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // LoadedImplementation: A successfully loaded active implementation
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export interface LoadedImplementation {
76
+ implId: string;
77
+ ruleId: string;
78
+ meta: RuleHostMeta;
79
+ /* eslint-disable no-unused-vars -- Reason: _input parameter name in interface type definition intentionally unused - actual implementation uses different param names */
80
+ evaluate: (_input: RuleHostInput) => RuleHostResult;
81
+ /* eslint-enable no-unused-vars */
82
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Rule Host — Constrained execution layer for active code implementations
3
+ *
4
+ * PURPOSE: Load active code implementations from the principle-tree ledger,
5
+ * execute them in a constrained node:vm context, and merge their decisions.
6
+ *
7
+ * ARCHITECTURE:
8
+ * - Constructor takes stateDir to access the principle-tree ledger
9
+ * - evaluate(input) loads active code implementations and runs them
10
+ * - Each implementation executes in an isolated vm context with minimal helpers
11
+ * - Decision merge: block short-circuits, requireApproval collects, allow is implicit
12
+ *
13
+ * SECURITY CONSTRAINTS (T-12-01, T-12-04):
14
+ * - Candidate code loads through a dedicated vm context, not the host realm
15
+ * - No importModuleDynamically callback
16
+ * - Helpers are a frozen object — implementations cannot modify the helper surface
17
+ *
18
+ * CONSERVATIVE DEGRADATION (T-12-02, D-08):
19
+ * - On ANY host error (load failure, eval error, vm error): return undefined
20
+ * - Never throw, never bypass downstream gates (Progressive Gate, Edit Verification)
21
+ */
22
+
23
+ import * as fs from 'fs';
24
+ import {
25
+ listImplementationsByLifecycleState,
26
+ } from './principle-tree-ledger.js';
27
+ import { loadEntrySource } from './code-implementation-storage.js';
28
+ import { createRuleHostHelpers } from './rule-host-helpers.js';
29
+ import { loadRuleImplementationModule } from './rule-implementation-runtime.js';
30
+ import type {
31
+ RuleHostInput,
32
+ RuleHostResult,
33
+ RuleHostMeta,
34
+ LoadedImplementation,
35
+ } from './rule-host-types.js';
36
+ import type { Implementation } from '../types/principle-tree-schema.js';
37
+
38
+ export interface RuleHostLogger {
39
+ /* eslint-disable no-unused-vars -- Reason: logger callback param name intentionally unused - callback only invoked for side effects */
40
+ warn?: (_message: string) => void;
41
+ /* eslint-enable no-unused-vars */
42
+ }
43
+
44
+ export class RuleHost {
45
+ private readonly stateDir: string;
46
+ private readonly logger: RuleHostLogger;
47
+
48
+ constructor(stateDir: string, logger: RuleHostLogger = console) {
49
+ this.stateDir = stateDir;
50
+ this.logger = logger;
51
+ }
52
+
53
+ /**
54
+ * Evaluate the input against all active code implementations.
55
+ *
56
+ * Returns:
57
+ * - undefined when no active code implementations exist (no opinion)
58
+ * - undefined when all implementations return allow or matched=false
59
+ * - { decision: 'block', ... } when any implementation returns block (short-circuits)
60
+ * - { decision: 'requireApproval', ... } when any implementation returns requireApproval
61
+ */
62
+ evaluate(input: RuleHostInput): RuleHostResult | undefined {
63
+ try {
64
+ // Load active code implementations from the ledger
65
+ const activeImpls = this._loadActiveCodeImplementations();
66
+
67
+ if (activeImpls.length === 0) {
68
+ return undefined;
69
+ }
70
+
71
+ // Merge decisions from all active implementations
72
+ // eslint-disable-next-line @typescript-eslint/init-declarations -- undefined is valid zero value, checked before use
73
+ let blocked: RuleHostResult | undefined;
74
+ const approvals: RuleHostResult[] = [];
75
+
76
+ for (const impl of activeImpls) {
77
+ try {
78
+ const result = impl.evaluate(input);
79
+
80
+ if (!result.matched) {
81
+ continue;
82
+ }
83
+
84
+ if (result.decision === 'block') {
85
+ blocked = result;
86
+ break; // Short-circuit on block
87
+ }
88
+
89
+ if (result.decision === 'requireApproval') {
90
+ approvals.push(result);
91
+ }
92
+ // 'allow' is implicit — no action needed
93
+ } catch (evalError: unknown) {
94
+ // Individual implementation error: log and continue (D-08)
95
+ this.logger.warn?.(
96
+ `[RuleHost] Implementation ${impl.implId} evaluation failed: ${String(evalError)}`
97
+ );
98
+ }
99
+ }
100
+
101
+ if (blocked) {
102
+ return blocked;
103
+ }
104
+
105
+ if (approvals.length > 0) {
106
+ // Merge multiple requireApproval results
107
+ return {
108
+ decision: 'requireApproval',
109
+ matched: true,
110
+ reason: approvals.map((a) => a.reason).join('; '),
111
+ diagnostics: approvals.reduce<Record<string, unknown>>(
112
+ (acc, a) => ({ ...acc, ...a.diagnostics }),
113
+ {}
114
+ ),
115
+ };
116
+ }
117
+
118
+ // All implementations returned allow or matched=false — no opinion
119
+ return undefined;
120
+ } catch (hostError: unknown) {
121
+ // Conservative degradation: log and return undefined (D-08)
122
+ this.logger.warn?.(
123
+ `[RuleHost] Host evaluation failed, degrading conservatively: ${String(hostError)}`
124
+ );
125
+ return undefined;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Load active code implementations from the ledger.
131
+ * Filters by type=code and lifecycleState=active, then attempts to
132
+ * compile each implementation's code asset via node:vm.
133
+ */
134
+ private _loadActiveCodeImplementations(): LoadedImplementation[] {
135
+ try {
136
+ const activeAllTypes = listImplementationsByLifecycleState(
137
+ this.stateDir,
138
+ 'active'
139
+ );
140
+
141
+ // Filter to code-type implementations only
142
+ const codeImpls = activeAllTypes.filter((impl) => impl.type === 'code');
143
+
144
+ if (codeImpls.length === 0) {
145
+ return [];
146
+ }
147
+
148
+ const loaded: LoadedImplementation[] = [];
149
+
150
+ for (const impl of codeImpls) {
151
+ try {
152
+ const loadedImpl = this._loadSingleImplementation(impl);
153
+ if (loadedImpl) {
154
+ loaded.push(loadedImpl);
155
+ }
156
+ } catch (loadError: unknown) {
157
+ // Individual load failure: log and skip
158
+ this.logger.warn?.(
159
+ `[RuleHost] Failed to load implementation ${impl.id}: ${String(loadError)}`
160
+ );
161
+ }
162
+ }
163
+
164
+ return loaded;
165
+ } catch (ledgerError: unknown) {
166
+ // Ledger access failure: log and return empty
167
+ this.logger.warn?.(
168
+ `[RuleHost] Failed to access ledger: ${String(ledgerError)}`
169
+ );
170
+ return [];
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Load and compile a single implementation from its code asset path.
176
+ *
177
+ * The implementation file is expected to export:
178
+ * - meta: { name, version, ruleId, coversCondition }
179
+ * - evaluate(input: RuleHostInput): RuleHostResult
180
+ *
181
+ * Uses the shared isolated runtime loader so candidate code does not execute
182
+ * in the host global realm.
183
+ */
184
+ private _loadSingleImplementation(
185
+ impl: Implementation
186
+ ): LoadedImplementation | null {
187
+ let sourceCode = loadEntrySource(this.stateDir, impl.id);
188
+ if (!sourceCode) {
189
+ const assetPath = impl.path;
190
+ if (!assetPath || !fs.existsSync(assetPath)) {
191
+ return null;
192
+ }
193
+
194
+ try {
195
+ sourceCode = fs.readFileSync(assetPath, 'utf-8');
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ try {
202
+ const moduleExports = loadRuleImplementationModule(sourceCode, impl.id);
203
+
204
+ if (!moduleExports || typeof moduleExports.evaluate !== 'function') {
205
+ return null;
206
+ }
207
+
208
+ const fallbackMeta: RuleHostMeta = {
209
+ name: impl.id,
210
+ version: impl.version,
211
+ ruleId: impl.ruleId,
212
+ coversCondition: impl.coversCondition,
213
+ };
214
+ const meta: RuleHostMeta =
215
+ moduleExports.meta && typeof moduleExports.meta === 'object'
216
+ ? (moduleExports.meta as RuleHostMeta)
217
+ : fallbackMeta;
218
+
219
+ // Return a loaded implementation that wraps the compiled evaluate
220
+ // with the actual helpers from the input at evaluation time
221
+ /* eslint-disable no-unused-vars -- Reason: type cast params intentionally unused - they're just type annotations, actual function uses different params */
222
+ const rawEvaluate = moduleExports.evaluate as (
223
+ _input: RuleHostInput,
224
+ _helpers: ReturnType<typeof createRuleHostHelpers>
225
+ ) => RuleHostResult;
226
+ /* eslint-enable no-unused-vars */
227
+
228
+ return {
229
+ implId: impl.id,
230
+ ruleId: impl.ruleId,
231
+ meta,
232
+ evaluate: (input: RuleHostInput): RuleHostResult => {
233
+ const frozenHelpers = createRuleHostHelpers(input);
234
+ return rawEvaluate(input, frozenHelpers);
235
+ },
236
+ };
237
+ } catch (compileError: unknown) {
238
+ // Compilation failure: log and skip
239
+ this.logger.warn?.(
240
+ `[RuleHost] Failed to compile implementation ${impl.id}: ${String(compileError)}`
241
+ );
242
+ return null;
243
+ }
244
+ }
245
+ }
@@ -0,0 +1,38 @@
1
+ import { nodeVm } from '../utils/node-vm-polyfill.js';
2
+
3
+ export interface RuleImplementationModuleExports {
4
+ meta?: unknown;
5
+ evaluate?: unknown;
6
+ }
7
+
8
+ function normalizeImplementationSource(sourceCode: string): string {
9
+ const withoutExports = sourceCode
10
+ .replace(/export\s+const\s+meta\s*=/, 'const meta =')
11
+ .replace(/export\s+function\s+evaluate\s*\(/, 'function evaluate(');
12
+
13
+ return `${withoutExports}
14
+ globalThis.__pdRuleModule = {
15
+ meta: typeof meta === 'undefined' ? undefined : meta,
16
+ evaluate: typeof evaluate === 'undefined' ? undefined : evaluate,
17
+ };`;
18
+ }
19
+
20
+ export function loadRuleImplementationModule(
21
+ sourceCode: string,
22
+ filename: string,
23
+ ): RuleImplementationModuleExports {
24
+ const context = nodeVm.createContext(Object.create(null));
25
+ const script = new nodeVm.Script(normalizeImplementationSource(sourceCode), {
26
+ filename,
27
+ });
28
+
29
+ script.runInContext(context, {
30
+ timeout: 1000,
31
+ displayErrors: true,
32
+ });
33
+
34
+ const moduleExports = (context as { __pdRuleModule?: RuleImplementationModuleExports }).__pdRuleModule;
35
+ delete (context as { __pdRuleModule?: RuleImplementationModuleExports }).__pdRuleModule;
36
+
37
+ return moduleExports ?? {};
38
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared type definitions for database operations.
3
+ * Kept separate to avoid circular dependencies between schema-definitions and migration-runner.
4
+ */
5
+
6
+ /* eslint-disable no-unused-vars -- Reason: interface method param names are part of type signature */
7
+
8
+ /** Minimal interface for better-sqlite3 Database instances. */
9
+ export interface Db {
10
+ exec(_sql: string): unknown;
11
+
12
+ get<T = unknown>(_sql: string, ..._params: unknown[]): T | undefined;
13
+
14
+ run(_sql: string, ..._params: unknown[]): unknown;
15
+ close(): void;
16
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Schema Management — Unified database schema definitions and migrations.
3
+ *
4
+ * Usage:
5
+ * // In a database class constructor:
6
+ * import { ensureDatabaseSchema } from './schema';
7
+ * ensureDatabaseSchema(db, 'trajectory.db');
8
+ *
9
+ * CLI:
10
+ * node scripts/db-migrate.mjs status
11
+ * node scripts/db-migrate.mjs run
12
+ */
13
+
14
+ export { MigrationRunner, ensureDatabaseSchema } from './migration-runner.js';
15
+ export { SCHEMAS, getCatalog, DB_TYPES } from './schema-definitions.js';
16
+ export { ALL_MIGRATIONS } from './migrations/index.js';
17
+
18
+ export type { Db } from './db-types.js';
19
+ export type {
20
+ Migration,
21
+ DbType,
22
+ TableDefinition,
23
+ ViewDefinition,
24
+ FtsDefinition,
25
+ SchemaCatalog,
26
+ } from './migration-runner.js';
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Migration Runner — Executes and tracks schema migrations.
3
+ *
4
+ * Each migration is versioned. The runner reads the current version from
5
+ * the `schema_version` table and only executes migrations with higher IDs.
6
+ *
7
+ * Usage:
8
+ * const runner = new MigrationRunner(db);
9
+ * runner.run(allMigrations.filter(m => m.db === 'trajectory.db'));
10
+ */
11
+
12
+ import type { Migration } from './schema-definitions.js';
13
+ import { getCatalog, type DbType } from './schema-definitions.js';
14
+ import type { Db } from './db-types.js';
15
+
16
+ export class MigrationRunner {
17
+ private readonly db: Db;
18
+
19
+ constructor(db: Db) {
20
+ this.db = db;
21
+ }
22
+
23
+ /**
24
+ * Run all pending migrations for a given database type.
25
+ * @returns Array of migration names that were applied
26
+ */
27
+ runMigrations(migrations: Migration[], _dbType: DbType): string[] {
28
+ // NOTE: _dbType is kept for API signature compatibility but migrations are pre-filtered by caller
29
+ void _dbType;
30
+ // Ensure schema_version table exists before anything else
31
+ this.ensureVersionTable();
32
+
33
+ const currentVersion = this.getCurrentVersion();
34
+ const applied: string[] = [];
35
+
36
+ // Filter to pending migrations (higher ID than current)
37
+ const pending = migrations
38
+ .filter(m => m.id > currentVersion)
39
+ .sort((a, b) => a.id.localeCompare(b.id));
40
+
41
+ for (const migration of pending) {
42
+
43
+ console.log(`[MigrationRunner] Applying ${migration.id}-${migration.name}...`);
44
+ try {
45
+ migration.up(this.db);
46
+ this.setVersion(migration.id);
47
+ applied.push(`${migration.id}-${migration.name}`);
48
+ } catch (err) {
49
+ throw new Error(
50
+ `Migration ${migration.id}-${migration.name} failed: ${String(err)}`,
51
+ { cause: err }
52
+ );
53
+ }
54
+ }
55
+
56
+ if (applied.length > 0) {
57
+
58
+ console.log(`[MigrationRunner] Applied ${applied.length} migration(s): ${applied.join(', ')}`);
59
+ }
60
+
61
+ return applied;
62
+ }
63
+
64
+ /**
65
+ * Apply all schema definitions (tables, indexes, views, FTS) for a database.
66
+ * This is a convenience method that creates everything from the schema catalog.
67
+ * Used for initial setup when no migrations exist yet.
68
+ */
69
+ applySchemaCatalog(dbType: DbType): void {
70
+ this.ensureVersionTable();
71
+
72
+ const catalog = getCatalog(dbType);
73
+
74
+ // Tables
75
+ for (const [key, table] of Object.entries(catalog.tables)) {
76
+ try {
77
+ this.db.exec(table.ddl);
78
+ } catch (err) {
79
+ throw new Error(`Failed to create table ${key}: ${String(err)}`, { cause: err });
80
+ }
81
+ // Indexes
82
+ for (const indexDdl of table.indexes ?? []) {
83
+ try {
84
+ this.db.exec(indexDdl);
85
+ } catch (err) {
86
+ throw new Error(`Failed to create index for ${key}: ${String(err)}`, { cause: err });
87
+ }
88
+ }
89
+ }
90
+
91
+ // Views
92
+ for (const [key, view] of Object.entries(catalog.views)) {
93
+ try {
94
+ this.db.exec(view.ddl);
95
+ } catch (err) {
96
+ throw new Error(`Failed to create view ${key}: ${String(err)}`, { cause: err });
97
+ }
98
+ }
99
+
100
+ // FTS5 virtual tables
101
+ for (const [key, fts] of Object.entries(catalog.fts)) {
102
+ try {
103
+ this.db.exec(fts.ddl);
104
+ } catch (err) {
105
+ throw new Error(`Failed to create FTS table ${key}: ${String(err)}`, { cause: err });
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Get the current migration version.
112
+ * Returns '000' if no version is set (fresh database).
113
+ */
114
+ getCurrentVersion(): string {
115
+ try {
116
+ const row = this.db.get<{ version: string }>(
117
+ 'SELECT version FROM schema_version ORDER BY version DESC LIMIT 1'
118
+ );
119
+ return row?.version ?? '000';
120
+ } catch {
121
+ return '000';
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get all available migration info for a database type.
127
+ */
128
+ getMigrationInfo(migrations: Migration[], dbType: DbType): {
129
+ id: string;
130
+ name: string;
131
+ applied: boolean;
132
+ }[] {
133
+ const currentVersion = this.getCurrentVersion();
134
+ return migrations
135
+ .filter(m => m.db === dbType)
136
+ .sort((a, b) => a.id.localeCompare(b.id))
137
+ .map(m => ({
138
+ id: m.id,
139
+ name: m.name,
140
+ applied: m.id <= currentVersion,
141
+ }));
142
+ }
143
+
144
+ /**
145
+ * Rollback the latest migration (if down migration is defined).
146
+ */
147
+ rollback(migrations: Migration[], dbType: DbType): string | null {
148
+ const currentVersion = this.getCurrentVersion();
149
+ if (currentVersion === '000') return null;
150
+
151
+ const migration = migrations.find(
152
+ m => m.db === dbType && m.id === currentVersion
153
+ );
154
+ if (!migration) return null;
155
+ if (!migration.down) {
156
+ throw new Error(`Migration ${migration.id}-${migration.name} has no down migration`);
157
+ }
158
+
159
+
160
+ console.log(`[MigrationRunner] Rolling back ${migration.id}-${migration.name}...`);
161
+ migration.down(this.db);
162
+
163
+ // Set version to previous migration
164
+ const previousMigrations = migrations
165
+ .filter(m => m.db === dbType && m.id < currentVersion)
166
+ .sort((a, b) => b.id.localeCompare(a.id));
167
+ const newVersion = previousMigrations[0]?.id ?? '000';
168
+ this.setVersion(newVersion);
169
+
170
+
171
+ console.log(`[MigrationRunner] Rolled back to ${newVersion}`);
172
+ return migration.id;
173
+ }
174
+
175
+ // -----------------------------------------------------------------------
176
+ // Private helpers
177
+ // -----------------------------------------------------------------------
178
+
179
+ private ensureVersionTable(): void {
180
+ this.db.exec(`
181
+ CREATE TABLE IF NOT EXISTS schema_version (
182
+ version TEXT NOT NULL DEFAULT '000'
183
+ )
184
+ `);
185
+ // Ensure at least one row exists
186
+ const count = this.db.get<{ cnt: number }>('SELECT COUNT(*) as cnt FROM schema_version');
187
+ if (!count || count.cnt === 0) {
188
+ this.db.exec("INSERT INTO schema_version (version) VALUES ('000')");
189
+ }
190
+ }
191
+
192
+ private setVersion(version: string): void {
193
+ this.db.exec(`UPDATE schema_version SET version = '${version}'`);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Factory: create a MigrationRunner and apply schema catalog for a given database.
199
+ * This is the main entry point for database classes.
200
+ */
201
+ export function ensureDatabaseSchema(db: Db, dbType: DbType): void {
202
+ const runner = new MigrationRunner(db);
203
+ runner.applySchemaCatalog(dbType);
204
+ }
205
+
206
+ // Re-export types
207
+ export type { Migration, DbType, TableDefinition, ViewDefinition, FtsDefinition, SchemaCatalog } from './schema-definitions.js';