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.
- package/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- 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';
|