principles-disciple 1.72.0 → 1.74.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/INSTALL.md +1 -3
- package/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +29 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/migration.ts +0 -1
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +71 -64
- package/src/hooks/gate.ts +183 -31
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +400 -440
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +59 -16
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +38 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +8 -20
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +7 -7
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/principles/THINKING_OS.md +3 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +7 -7
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/principles/THINKING_OS.md +3 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/migration.test.ts +7 -7
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-context.test.ts +2 -2
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +191 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-removal.test.ts +188 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +32 -17
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +3 -1
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
|
@@ -7,32 +7,32 @@ export const PDTaskService: OpenClawPluginService = {
|
|
|
7
7
|
|
|
8
8
|
async start(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
9
9
|
const {workspaceDir} = ctx;
|
|
10
|
+
const logger = ctx.logger ?? ctx.api?.logger;
|
|
10
11
|
if (!workspaceDir) {
|
|
11
|
-
|
|
12
|
+
logger?.warn?.(`[PD:TaskManager] No workspaceDir, skipping PD task reconciliation`);
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
logger.info?.(`[PD:TaskManager] Starting PD task reconciliation...`);
|
|
16
|
+
logger?.info?.(`[PD:TaskManager] Starting PD task reconciliation...`);
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
const result = await reconcilePDTasks(workspaceDir, { logger });
|
|
20
|
-
logger
|
|
19
|
+
const result = await reconcilePDTasks(workspaceDir, { logger: logger ?? console });
|
|
20
|
+
logger?.info?.(
|
|
21
21
|
`[PD:TaskManager] Reconcile complete: +${result.created.length} ~${result.updated.length} =${result.skipped.length} orphan=${result.orphaned.length}`,
|
|
22
22
|
);
|
|
23
23
|
if (result.created.length > 0) {
|
|
24
|
-
logger
|
|
24
|
+
logger?.info?.(`[PD:TaskManager] Created jobs: ${result.created.join(', ')}`);
|
|
25
25
|
}
|
|
26
26
|
if (result.updated.length > 0) {
|
|
27
|
-
logger
|
|
27
|
+
logger?.info?.(`[PD:TaskManager] Updated jobs: ${result.updated.join(', ')}`);
|
|
28
28
|
}
|
|
29
29
|
if (result.errors.length > 0) {
|
|
30
|
-
logger
|
|
30
|
+
logger?.warn?.(
|
|
31
31
|
`[PD:TaskManager] Reconcile errors: ${result.errors.map((e) => e.message).join(', ')}`,
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
} catch (err) {
|
|
35
|
-
logger
|
|
35
|
+
logger?.warn?.(`[PD:TaskManager] Reconcile failed: ${String(err)}`);
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
|
|
@@ -1,128 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type {
|
|
2
|
+
PDTaskSchedule as CorePDTaskSchedule,
|
|
3
|
+
PDTaskExecution as CorePDTaskExecution,
|
|
4
|
+
PDTaskDelivery as CorePDTaskDelivery,
|
|
5
|
+
PDTaskMeta as CorePDTaskMeta,
|
|
6
|
+
PDTaskSpec as CorePDTaskSpec,
|
|
7
|
+
} from '@principles/core/runtime-v2';
|
|
8
|
+
|
|
9
|
+
export type PDTaskSchedule = CorePDTaskSchedule;
|
|
10
|
+
export type PDTaskExecution = CorePDTaskExecution;
|
|
11
|
+
export type PDTaskDelivery = CorePDTaskDelivery;
|
|
12
|
+
export type PDTaskMeta = CorePDTaskMeta;
|
|
13
|
+
export type PDTaskSpec = CorePDTaskSpec;
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
PDTaskScheduleSchema,
|
|
17
|
+
PDTaskExecutionSchema,
|
|
18
|
+
PDTaskDeliverySchema,
|
|
19
|
+
PDTaskMetaSchema,
|
|
20
|
+
PDTaskSpecSchema,
|
|
21
|
+
} from '@principles/core/runtime-v2';
|
|
22
|
+
|
|
23
|
+
export const BUILTIN_PD_TASKS: PDTaskSpec[] = [];
|
|
8
24
|
|
|
9
|
-
// =========================================================================
|
|
10
|
-
// PDTaskSpec — Declaration Schema
|
|
11
|
-
// =========================================================================
|
|
12
|
-
|
|
13
|
-
/** Cron schedule for PD tasks (only "every" kind supported for now) */
|
|
14
|
-
export interface PDTaskSchedule {
|
|
15
|
-
kind: 'every';
|
|
16
|
-
everyMs: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Execution configuration for a PD task */
|
|
20
|
-
export interface PDTaskExecution {
|
|
21
|
-
/** Which prompt builder to use */
|
|
22
|
-
promptTemplate: string;
|
|
23
|
-
/** Execution timeout in seconds (default: 120) */
|
|
24
|
-
timeoutSeconds?: number;
|
|
25
|
-
/** Use lightweight context to save tokens */
|
|
26
|
-
lightContext?: boolean;
|
|
27
|
-
/** Restrict available tools */
|
|
28
|
-
toolsAllow?: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Delivery configuration for task results */
|
|
32
|
-
export interface PDTaskDelivery {
|
|
33
|
-
mode: 'none' | 'announce';
|
|
34
|
-
channel?: string;
|
|
35
|
-
to?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Metadata — not synced to cron, used for health tracking */
|
|
39
|
-
export interface PDTaskMeta {
|
|
40
|
-
/** When this task was first declared */
|
|
41
|
-
createdAtMs?: number;
|
|
42
|
-
/** Last successful reconcile timestamp */
|
|
43
|
-
lastSyncedAtMs?: number;
|
|
44
|
-
/** The cron job ID from last sync */
|
|
45
|
-
lastSyncedJobId?: string;
|
|
46
|
-
/** Last sync status */
|
|
47
|
-
lastSyncStatus?: 'ok' | 'error';
|
|
48
|
-
/** Last sync error message */
|
|
49
|
-
lastSyncError?: string;
|
|
50
|
-
/** Consecutive failure count (from CronJobState.consecutiveErrors) */
|
|
51
|
-
consecutiveFailCount?: number;
|
|
52
|
-
/** Timestamp of last failure */
|
|
53
|
-
lastFailedAtMs?: number;
|
|
54
|
-
/** Whether this task was auto-disabled due to health issues */
|
|
55
|
-
autoDisabled?: boolean;
|
|
56
|
-
/** When the task was auto-disabled */
|
|
57
|
-
autoDisabledAt?: number;
|
|
58
|
-
/** Reason for auto-disable */
|
|
59
|
-
autoDisabledReason?: string;
|
|
60
|
-
/** Last manual trigger timestamp */
|
|
61
|
-
lastTriggeredAtMs?: number;
|
|
62
|
-
/** Last manual trigger status */
|
|
63
|
-
lastTriggerStatus?: 'succeeded' | 'failed' | 'pending';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* PDTaskSpec — A declarative specification for a PD background task.
|
|
68
|
-
*
|
|
69
|
-
* This is the source of truth. The reconciler translates these into
|
|
70
|
-
* CronJob entries in OpenClaw's cron/jobs.json.
|
|
71
|
-
*/
|
|
72
|
-
export interface PDTaskSpec {
|
|
73
|
-
/** Stable unique ID — never changes across versions */
|
|
74
|
-
id: string;
|
|
75
|
-
/** Human-readable name — becomes the CronJob name (must start with "PD ") */
|
|
76
|
-
name: string;
|
|
77
|
-
/** Description shown to users */
|
|
78
|
-
description: string;
|
|
79
|
-
/** Whether this task should be active */
|
|
80
|
-
enabled: boolean;
|
|
81
|
-
/** Schema version — bumped when prompt/config changes require re-sync */
|
|
82
|
-
version: string;
|
|
83
|
-
/** Cron schedule (only "every" kind supported for now) */
|
|
84
|
-
schedule: PDTaskSchedule;
|
|
85
|
-
/** OpenClaw agent ID to run under (default: "main") */
|
|
86
|
-
agentId?: string;
|
|
87
|
-
/** Execution configuration */
|
|
88
|
-
execution: PDTaskExecution;
|
|
89
|
-
/** Delivery configuration */
|
|
90
|
-
delivery: PDTaskDelivery;
|
|
91
|
-
/** Metadata — not synced to cron */
|
|
92
|
-
meta?: PDTaskMeta;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// =========================================================================
|
|
96
|
-
// Builtin PD Tasks
|
|
97
|
-
// =========================================================================
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Built-in PD tasks declared by the plugin.
|
|
101
|
-
*
|
|
102
|
-
* These are reconciled on plugin startup. Adding a new task here
|
|
103
|
-
* automatically creates the corresponding cron job on next restart.
|
|
104
|
-
*/
|
|
105
|
-
export const BUILTIN_PD_TASKS: PDTaskSpec[] = [
|
|
106
|
-
{
|
|
107
|
-
id: 'empathy-optimizer',
|
|
108
|
-
name: 'PD Empathy Optimizer',
|
|
109
|
-
description:
|
|
110
|
-
'Analyzes recent user messages to discover new frustration expressions and optimize keyword weights.',
|
|
111
|
-
enabled: true,
|
|
112
|
-
version: '1.0.1', // Bumped to force cron job settings update
|
|
113
|
-
schedule: {
|
|
114
|
-
kind: 'every',
|
|
115
|
-
everyMs: 5 * 60 * 1000, // 5 minutes (testing); increase to 6h once stable
|
|
116
|
-
},
|
|
117
|
-
agentId: 'main',
|
|
118
|
-
execution: {
|
|
119
|
-
promptTemplate: 'empathy-optimizer',
|
|
120
|
-
timeoutSeconds: 300, // 5 min — needs time to scan events.jsonl
|
|
121
|
-
lightContext: true,
|
|
122
|
-
toolsAllow: ['read_file', 'write_file', 'search_file_content'],
|
|
123
|
-
},
|
|
124
|
-
delivery: {
|
|
125
|
-
mode: 'none',
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
];
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// --- Mocks (hoisted for vi.mock compatibility) ---
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
mockCollect, mockRegister, mockCreateAssetDir,
|
|
7
|
+
mockLoadModule, mockValidate, mockGenerate,
|
|
8
|
+
} = vi.hoisted(() => ({
|
|
9
|
+
mockCollect: vi.fn(),
|
|
10
|
+
mockRegister: vi.fn(),
|
|
11
|
+
mockCreateAssetDir: vi.fn(),
|
|
12
|
+
mockLoadModule: vi.fn(),
|
|
13
|
+
mockValidate: vi.fn(),
|
|
14
|
+
mockGenerate: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock('../code-validator.js', () => ({
|
|
18
|
+
validateGeneratedCode: mockValidate,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('../template-generator.js', () => ({
|
|
22
|
+
generateFromTemplate: mockGenerate,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('../ledger-registrar.js', () => ({
|
|
26
|
+
registerCompiledRule: mockRegister,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('../../code-implementation-storage.js', () => ({
|
|
30
|
+
createImplementationAssetDir: mockCreateAssetDir,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock('../../rule-implementation-runtime.js', () => ({
|
|
34
|
+
loadRuleImplementationModule: mockLoadModule,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
import { PrincipleCompiler } from '../compiler.js';
|
|
38
|
+
|
|
39
|
+
// --- Fixtures ---
|
|
40
|
+
|
|
41
|
+
const PRINCIPLE_ID = 'P_test_001';
|
|
42
|
+
|
|
43
|
+
function makeContext(overrides?: { reason?: string }) {
|
|
44
|
+
return {
|
|
45
|
+
principle: {
|
|
46
|
+
id: PRINCIPLE_ID,
|
|
47
|
+
version: 1,
|
|
48
|
+
text: 'Never delete system files via bash',
|
|
49
|
+
triggerPattern: 'bash rm',
|
|
50
|
+
action: 'block dangerous bash commands',
|
|
51
|
+
status: 'active' as const,
|
|
52
|
+
priority: 'high' as const,
|
|
53
|
+
scope: 'global' as const,
|
|
54
|
+
evaluability: 'deterministic' as const,
|
|
55
|
+
valueScore: 0.9,
|
|
56
|
+
adherenceRate: 0.8,
|
|
57
|
+
painPreventedCount: 5,
|
|
58
|
+
derivedFromPainIds: ['pain_001'],
|
|
59
|
+
ruleIds: [] as string[],
|
|
60
|
+
conflictsWithPrincipleIds: [] as string[],
|
|
61
|
+
createdAt: '2026-05-01T00:00:00Z',
|
|
62
|
+
updatedAt: '2026-05-01T00:00:00Z',
|
|
63
|
+
},
|
|
64
|
+
painEvents: [
|
|
65
|
+
{
|
|
66
|
+
reason: overrides?.reason ?? 'bash command failed on /etc/important.conf',
|
|
67
|
+
source: 'tool_failure',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
sessionSnapshot: null,
|
|
71
|
+
lineage: { sourcePainIds: ['pain_001'], sessionId: null as string | null },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- Tests ---
|
|
76
|
+
|
|
77
|
+
describe('PrincipleCompiler replay gate (PRI-115)', () => {
|
|
78
|
+
let compiler: PrincipleCompiler;
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
vi.clearAllMocks();
|
|
82
|
+
|
|
83
|
+
// Mock collector.collect directly on the instance
|
|
84
|
+
mockCollect.mockReturnValue(makeContext());
|
|
85
|
+
|
|
86
|
+
mockValidate.mockReturnValue({ valid: true, errors: [], warnings: [] });
|
|
87
|
+
mockGenerate.mockReturnValue(
|
|
88
|
+
'export function evaluate() { return { decision: "block", matched: true, reason: "test", confidence: 0.95 }; }',
|
|
89
|
+
);
|
|
90
|
+
mockRegister.mockReturnValue({ ruleId: 'R_test_auto', implementationId: 'IMPL_test_auto' });
|
|
91
|
+
|
|
92
|
+
compiler = new PrincipleCompiler('/tmp/test-state', {} as any);
|
|
93
|
+
// Override collector after construction to avoid module resolution issues
|
|
94
|
+
(compiler as any).collector = { collect: mockCollect, collectBatch: vi.fn() };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('failing replay returns degraded=true and blocks registration', () => {
|
|
98
|
+
mockCollect.mockReturnValue(makeContext());
|
|
99
|
+
|
|
100
|
+
mockLoadModule.mockReturnValue({
|
|
101
|
+
evaluate: () => ({ decision: 'allow', matched: false, reason: 'pass', confidence: 1.0 }),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = compiler.compileOne(PRINCIPLE_ID);
|
|
105
|
+
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
expect(result.degraded).toBe(true);
|
|
108
|
+
expect(result.reason).toBe('replay_validation_failed');
|
|
109
|
+
expect(result.replayResult).toBeDefined();
|
|
110
|
+
expect(result.replayResult!.passed).toBe(false);
|
|
111
|
+
expect(mockRegister).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('no asset dir created on replay failure', () => {
|
|
115
|
+
mockCollect.mockReturnValue(makeContext());
|
|
116
|
+
|
|
117
|
+
mockLoadModule.mockReturnValue({
|
|
118
|
+
evaluate: () => ({ decision: 'allow', matched: false, reason: 'pass', confidence: 1.0 }),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
compiler.compileOne(PRINCIPLE_ID);
|
|
122
|
+
|
|
123
|
+
expect(mockCreateAssetDir).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('passing replay registers normally', () => {
|
|
127
|
+
mockCollect.mockReturnValue(makeContext());
|
|
128
|
+
|
|
129
|
+
mockLoadModule.mockReturnValue({
|
|
130
|
+
evaluate: (input: any) => {
|
|
131
|
+
const path = input?.action?.paramsSummary?.path;
|
|
132
|
+
if (path && path.includes('passwd')) {
|
|
133
|
+
return { decision: 'block', matched: true, reason: 'dangerous', confidence: 0.95 };
|
|
134
|
+
}
|
|
135
|
+
return { decision: 'allow', matched: false, reason: 'safe', confidence: 1.0 };
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const result = compiler.compileOne(PRINCIPLE_ID);
|
|
140
|
+
|
|
141
|
+
expect(result.success).toBe(true);
|
|
142
|
+
expect(result.degraded).toBeUndefined();
|
|
143
|
+
expect(mockRegister).toHaveBeenCalledOnce();
|
|
144
|
+
expect(mockCreateAssetDir).toHaveBeenCalledOnce();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('no cases (no regex qualifier) skips replay gate and registers', () => {
|
|
148
|
+
mockCollect.mockReturnValue(makeContext({ reason: 'bash failed unexpectedly' }));
|
|
149
|
+
|
|
150
|
+
mockLoadModule.mockReturnValue({
|
|
151
|
+
evaluate: () => ({ decision: 'block', matched: true, reason: 'test', confidence: 0.95 }),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const result = compiler.compileOne(PRINCIPLE_ID);
|
|
155
|
+
|
|
156
|
+
expect(result.success).toBe(true);
|
|
157
|
+
expect(result.degraded).toBeUndefined();
|
|
158
|
+
expect(mockRegister).toHaveBeenCalledOnce();
|
|
159
|
+
expect(mockLoadModule).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('no evaluate export returns degraded', () => {
|
|
163
|
+
mockCollect.mockReturnValue(makeContext());
|
|
164
|
+
|
|
165
|
+
mockLoadModule.mockReturnValue({});
|
|
166
|
+
|
|
167
|
+
const result = compiler.compileOne(PRINCIPLE_ID);
|
|
168
|
+
|
|
169
|
+
expect(result.success).toBe(false);
|
|
170
|
+
expect(result.degraded).toBe(true);
|
|
171
|
+
expect(result.reason).toContain('no evaluate export');
|
|
172
|
+
expect(mockRegister).not.toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -1,45 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Code Validator — Validates LLM-generated rule implementation code
|
|
3
3
|
*
|
|
4
|
-
* PURPOSE: Ensure generated code is safe, syntactically correct, and exports
|
|
5
|
-
* the expected shape before it is stored as a rule implementation.
|
|
6
|
-
*
|
|
7
4
|
* CHECKS:
|
|
8
|
-
* 1. Syntax: code parses without errors
|
|
9
|
-
* 2. Forbidden patterns:
|
|
10
|
-
* 3. Export check: sandbox loads and exports evaluate + meta
|
|
11
|
-
* 4. Return shape: evaluate(mockInput) returns { matched: boolean }
|
|
5
|
+
* 1. Syntax: code parses without errors (VM)
|
|
6
|
+
* 2. Forbidden patterns: delegates to core checkForbiddenPatterns (PRI-44)
|
|
7
|
+
* 3. Export check: sandbox loads and exports evaluate + meta (VM)
|
|
8
|
+
* 4. Return shape: evaluate(mockInput) returns { matched: boolean } (VM)
|
|
12
9
|
*
|
|
13
|
-
*
|
|
10
|
+
* PRI-44: Forbidden pattern detection extracted to @principles/core.
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
import { nodeVm } from '../../utils/node-vm-polyfill.js';
|
|
17
14
|
import { loadRuleImplementationModule } from '../rule-implementation-runtime.js';
|
|
18
|
-
|
|
19
|
-
export interface ValidationResult {
|
|
20
|
-
valid: boolean;
|
|
21
|
-
errors: string[];
|
|
22
|
-
warnings: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const FORBIDDEN_PATTERNS: { pattern: RegExp; label: string }[] = [
|
|
26
|
-
{ pattern: /\brequire\s*\(/, label: 'require' },
|
|
27
|
-
{ pattern: /\bimport\s+/, label: 'import' },
|
|
28
|
-
{ pattern: /\bfetch\s*\(/, label: 'fetch' },
|
|
29
|
-
{ pattern: /\beval\s*\(/, label: 'eval' },
|
|
30
|
-
{ pattern: /\bFunction\s*\(/, label: 'Function' },
|
|
31
|
-
{ pattern: /\bprocess\b/, label: 'process' },
|
|
32
|
-
{ pattern: /\bglobalThis\b/, label: 'globalThis' },
|
|
33
|
-
{ pattern: /\bglobal\b/, label: 'global' },
|
|
34
|
-
{ pattern: /\bReflect\b/, label: 'Reflect' },
|
|
35
|
-
{ pattern: /\bProxy\b/, label: 'Proxy' },
|
|
36
|
-
{ pattern: /\bconstructor\b/, label: 'constructor' },
|
|
37
|
-
{ pattern: /\bBuffer\b/, label: 'Buffer' },
|
|
38
|
-
{ pattern: /\bsetTimeout\b/, label: 'setTimeout' },
|
|
39
|
-
{ pattern: /\bsetInterval\b/, label: 'setInterval' },
|
|
40
|
-
// Bracket notation access to globals
|
|
41
|
-
{ pattern: /\[\s*['"](require|import|fetch|eval|process|globalThis|global|Reflect|Proxy|Buffer|Function)\s*['"]\s*\]/, label: 'bracket access to forbidden global' },
|
|
42
|
-
];
|
|
15
|
+
import { checkForbiddenPatterns, type ValidationResult } from '@principles/core/runtime-v2';
|
|
43
16
|
|
|
44
17
|
const MOCK_INPUT = {
|
|
45
18
|
action: {
|
|
@@ -53,12 +26,13 @@ const MOCK_INPUT = {
|
|
|
53
26
|
derived: { estimatedLineChanges: 0, bashRisk: 'safe' },
|
|
54
27
|
};
|
|
55
28
|
|
|
29
|
+
export type { ValidationResult } from '@principles/core/runtime-v2';
|
|
30
|
+
|
|
56
31
|
export function validateGeneratedCode(code: string): ValidationResult {
|
|
57
32
|
const errors: string[] = [];
|
|
58
33
|
const warnings: string[] = [];
|
|
59
34
|
|
|
60
35
|
// --- Check 1: Syntax ---
|
|
61
|
-
// Normalize export keywords so vm.Script can parse ES module source
|
|
62
36
|
const normalized = code
|
|
63
37
|
.replace(/export\s+const\s+/g, 'const ')
|
|
64
38
|
.replace(/export\s+function\s+/g, 'function ');
|
|
@@ -69,11 +43,10 @@ export function validateGeneratedCode(code: string): ValidationResult {
|
|
|
69
43
|
return { valid: false, errors, warnings };
|
|
70
44
|
}
|
|
71
45
|
|
|
72
|
-
// --- Check 2: Forbidden patterns ---
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
46
|
+
// --- Check 2: Forbidden patterns (delegated to core) ---
|
|
47
|
+
const forbiddenLabels = checkForbiddenPatterns(code);
|
|
48
|
+
for (const label of forbiddenLabels) {
|
|
49
|
+
errors.push(`Forbidden pattern: ${label}`);
|
|
77
50
|
}
|
|
78
51
|
|
|
79
52
|
if (errors.length > 0) {
|
|
@@ -102,6 +75,9 @@ export function validateGeneratedCode(code: string): ValidationResult {
|
|
|
102
75
|
}
|
|
103
76
|
|
|
104
77
|
// --- Check 4: Return shape ---
|
|
78
|
+
// evaluate() throwing on mock input is acceptable — the function exists and has the
|
|
79
|
+
// right signature, it just can't handle our generic mock data.
|
|
80
|
+
// Track as a non-blocking warning so operators know the rule may be fragile.
|
|
105
81
|
try {
|
|
106
82
|
const result = (moduleExports.evaluate as (input: unknown) => unknown)(MOCK_INPUT);
|
|
107
83
|
if (!result || typeof result !== 'object') {
|
|
@@ -110,9 +86,6 @@ export function validateGeneratedCode(code: string): ValidationResult {
|
|
|
110
86
|
errors.push('evaluate must return { matched: boolean }');
|
|
111
87
|
}
|
|
112
88
|
} catch (evalWarning) {
|
|
113
|
-
// evaluate throwing on mock input is acceptable — the function exists and
|
|
114
|
-
// has the right signature, it just can't handle our generic mock data.
|
|
115
|
-
// Track as a non-blocking warning so operators know the rule may be fragile.
|
|
116
89
|
warnings.push(`evaluate() threw on mock input: ${(evalWarning as Error).message}`);
|
|
117
90
|
}
|
|
118
91
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Orchestrates the full compilation flow:
|
|
5
5
|
* ReflectionContextCollector.collect() → extract patterns → generateFromTemplate()
|
|
6
|
-
* → validateGeneratedCode() → registerCompiledRule()
|
|
6
|
+
* → validateGeneratedCode() → [replay validation] → registerCompiledRule()
|
|
7
7
|
*
|
|
8
8
|
* DESIGN DECISIONS:
|
|
9
9
|
* - extractPatterns infers toolName from pain event reasons and session tool calls
|
|
10
10
|
* - Groups by toolName into PainPattern objects
|
|
11
11
|
* - If no patterns can be extracted, returns a 'no patterns' failure
|
|
12
|
+
* - PRI-115: Replay validation gate runs after code validation, before registration
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { ReflectionContextCollector } from '../reflection/reflection-context.js';
|
|
@@ -17,19 +18,13 @@ import { generateFromTemplate, type PainPattern } from './template-generator.js'
|
|
|
17
18
|
import { registerCompiledRule } from './ledger-registrar.js';
|
|
18
19
|
import { createImplementationAssetDir } from '../code-implementation-storage.js';
|
|
19
20
|
import type { TrajectoryDatabase } from '../trajectory.js';
|
|
21
|
+
import type { CompileResult } from '@principles/core/runtime-v2';
|
|
22
|
+
import { loadRuleImplementationModule } from '../rule-implementation-runtime.js';
|
|
23
|
+
import { createGoldenTraceFixture, type GoldenTraceCase } from '@principles/core/runtime-v2';
|
|
24
|
+
import { replayGoldenTrace, type ReplayEvaluateFn } from '@principles/core/runtime-v2';
|
|
20
25
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
export interface CompileResult {
|
|
26
|
-
success: boolean;
|
|
27
|
-
principleId: string;
|
|
28
|
-
ruleId?: string;
|
|
29
|
-
implementationId?: string;
|
|
30
|
-
code?: string;
|
|
31
|
-
reason?: string;
|
|
32
|
-
}
|
|
26
|
+
// Re-export CompileResult from core
|
|
27
|
+
export type { CompileResult } from '@principles/core/runtime-v2';
|
|
33
28
|
|
|
34
29
|
// ---------------------------------------------------------------------------
|
|
35
30
|
// Constants
|
|
@@ -119,13 +114,20 @@ function extractPatterns(context: {
|
|
|
119
114
|
/**
|
|
120
115
|
* Infer tool name from text by checking for known tool names.
|
|
121
116
|
* Returns the first matching known tool name, or null if none found.
|
|
117
|
+
*
|
|
118
|
+
* Uses negative lookbehind to avoid matching natural-language uses:
|
|
119
|
+
* - "please read the error" → read is a verb, not a tool reference
|
|
120
|
+
* - "could write to file" → write is a verb, not a tool reference
|
|
121
|
+
* Tool references in pain events typically appear near words like
|
|
122
|
+
* "tool", "call", "command", "failed", "via", "using", etc.
|
|
122
123
|
*/
|
|
123
124
|
function inferToolName(text: string): string | null {
|
|
124
125
|
const lower = text.toLowerCase();
|
|
125
126
|
for (const tool of KNOWN_TOOLS) {
|
|
126
|
-
// Match as a standalone word
|
|
127
|
+
// Match as a standalone word but exclude common natural-language patterns
|
|
127
128
|
// e.g., "bash" in "bash" or "bash command" but not in "ambush"
|
|
128
|
-
|
|
129
|
+
// and not in "please read" or "could write" where it's a verb
|
|
130
|
+
const regex = new RegExp(`(?<!please |could |should |would )\\b${tool}\\b`);
|
|
129
131
|
if (regex.test(lower)) {
|
|
130
132
|
return tool;
|
|
131
133
|
}
|
|
@@ -173,6 +175,7 @@ export class PrincipleCompiler {
|
|
|
173
175
|
* 2. Extract pain patterns
|
|
174
176
|
* 3. Generate code from template
|
|
175
177
|
* 4. Validate generated code
|
|
178
|
+
* 4.5. Replay validation against GoldenTrace (PRI-115)
|
|
176
179
|
* 5. Register in ledger
|
|
177
180
|
*/
|
|
178
181
|
compileOne(principleId: string): CompileResult {
|
|
@@ -205,6 +208,49 @@ export class PrincipleCompiler {
|
|
|
205
208
|
};
|
|
206
209
|
}
|
|
207
210
|
|
|
211
|
+
// Step 4.5: Replay validation against GoldenTrace (PRI-115)
|
|
212
|
+
const replayCases = this.buildGoldenTraceCases(patterns, context);
|
|
213
|
+
if (replayCases.length > 0) {
|
|
214
|
+
let moduleExports: { evaluate?: unknown };
|
|
215
|
+
try {
|
|
216
|
+
moduleExports = loadRuleImplementationModule(code, `replay-${principleId}.js`);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
principleId,
|
|
221
|
+
reason: `module_load_error: ${(err as Error).message}`,
|
|
222
|
+
code,
|
|
223
|
+
degraded: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof moduleExports.evaluate !== 'function') {
|
|
228
|
+
return { success: false, principleId, reason: 'replay: no evaluate export', degraded: true };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const evaluateFn = moduleExports.evaluate as ReplayEvaluateFn;
|
|
233
|
+
const replayResult = replayGoldenTrace(evaluateFn, replayCases);
|
|
234
|
+
if (!replayResult.passed) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
principleId,
|
|
238
|
+
reason: 'replay_validation_failed',
|
|
239
|
+
code,
|
|
240
|
+
replayResult,
|
|
241
|
+
degraded: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
principleId,
|
|
248
|
+
reason: `replay_error: ${(err as Error).message}`,
|
|
249
|
+
code,
|
|
250
|
+
degraded: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
208
254
|
// Step 5: Register
|
|
209
255
|
const registration = registerCompiledRule(this.stateDir, {
|
|
210
256
|
principleId,
|
|
@@ -226,6 +272,43 @@ export class PrincipleCompiler {
|
|
|
226
272
|
};
|
|
227
273
|
}
|
|
228
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Build GoldenTrace test cases from extracted pain patterns.
|
|
277
|
+
*
|
|
278
|
+
* Generates synthetic negative/positive parameter pairs based on whether
|
|
279
|
+
* the pattern targets commands (commandRegex) or paths (pathRegex).
|
|
280
|
+
* Returns an empty array when no patterns are available.
|
|
281
|
+
*/
|
|
282
|
+
private buildGoldenTraceCases(
|
|
283
|
+
patterns: PainPattern[],
|
|
284
|
+
_context: { painEvents: Array<{ reason: string | null; source: string }> },
|
|
285
|
+
): GoldenTraceCase[] {
|
|
286
|
+
if (patterns.length === 0) return [];
|
|
287
|
+
|
|
288
|
+
const pattern = patterns[0];
|
|
289
|
+
// Skip replay when the pattern has no regex qualifier -- the generated template
|
|
290
|
+
// blocks ALL calls to the tool, making it impossible to construct a passing
|
|
291
|
+
// positive case. Replay is only meaningful when the template is selective.
|
|
292
|
+
// Also skip contentRegex-only patterns: synthetic content params are not meaningful.
|
|
293
|
+
if (!pattern.commandRegex && !pattern.pathRegex) return [];
|
|
294
|
+
const negativeParams: Record<string, unknown> = {};
|
|
295
|
+
if (pattern.commandRegex) negativeParams.command = 'rm -rf /';
|
|
296
|
+
else negativeParams.path = '/etc/passwd';
|
|
297
|
+
|
|
298
|
+
const positiveParams: Record<string, unknown> = {};
|
|
299
|
+
if (pattern.commandRegex) positiveParams.command = 'echo hello';
|
|
300
|
+
else positiveParams.path = '/tmp/safe.txt';
|
|
301
|
+
|
|
302
|
+
const fixture = createGoldenTraceFixture({
|
|
303
|
+
toolName: pattern.toolName,
|
|
304
|
+
negativeParams,
|
|
305
|
+
positiveParams,
|
|
306
|
+
expectedDecision: 'block',
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return fixture.cases;
|
|
310
|
+
}
|
|
311
|
+
|
|
229
312
|
/**
|
|
230
313
|
* Compile all eligible principles (those with derivedFromPainIds).
|
|
231
314
|
*/
|
|
@@ -235,6 +318,8 @@ export class PrincipleCompiler {
|
|
|
235
318
|
try {
|
|
236
319
|
return this.compileOne(ctx.principle.id);
|
|
237
320
|
} catch (e) {
|
|
321
|
+
// Log for operator visibility — catch-return is intentional for batch容错
|
|
322
|
+
console.warn(`[PrincipleCompiler] compileAll failed for ${ctx.principle.id}: ${(e as Error).message}`);
|
|
238
323
|
return { success: false, principleId: ctx.principle.id, reason: `unhandled: ${(e as Error).message}` };
|
|
239
324
|
}
|
|
240
325
|
});
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Principle Compiler — Barrel Export
|
|
3
3
|
*
|
|
4
4
|
* Re-exports all principle-compiler components for convenient importing.
|
|
5
|
+
* PRI-44: Types and pure logic re-exported from @principles/core.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
export { PrincipleCompiler
|
|
8
|
-
export {
|
|
8
|
+
export { PrincipleCompiler } from './compiler.js';
|
|
9
|
+
export type { CompileResult } from './compiler.js';
|
|
10
|
+
export { validateGeneratedCode } from './code-validator.js';
|
|
11
|
+
export type { ValidationResult } from './code-validator.js';
|
|
9
12
|
export { generateFromTemplate, type PainPattern } from './template-generator.js';
|
|
10
13
|
export { registerCompiledRule, type RegisterInput, type RegisterResult } from './ledger-registrar.js';
|