principles-disciple 1.72.0 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +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/confirm-first-gate.ts +255 -0
- 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 +38 -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/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- 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 +46 -44
- package/src/hooks/gate.ts +207 -7
- 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 +459 -439
- 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 +39 -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 +10 -0
- 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 +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -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/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- 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 +1 -1
- 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 +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -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/runtime/.gitignore +2 -2
- 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 +2 -2
- 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 +1 -1
- 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/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/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-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -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-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/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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { recommendLifecycleRoute } from '@principles/core/runtime-v2';
|
|
3
3
|
import type {
|
|
4
4
|
PrincipleLifecycleEvidence,
|
|
5
5
|
RuleLifecycleEvidence,
|
|
6
|
-
} from '
|
|
6
|
+
} from '@principles/core/runtime-v2';
|
|
7
7
|
|
|
8
8
|
function createRuleEvidence(id: string, overrides: Partial<RuleLifecycleEvidence> = {}): RuleLifecycleEvidence {
|
|
9
9
|
return {
|
|
@@ -94,9 +94,9 @@ function createPrincipleEvidence(
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
describe('
|
|
97
|
+
describe('recommendLifecycleRoute', () => {
|
|
98
98
|
it('recommends code for deterministic high-risk principles with repeated failures and sufficient replay evidence', () => {
|
|
99
|
-
const recommendation =
|
|
99
|
+
const recommendation = recommendLifecycleRoute(
|
|
100
100
|
createPrincipleEvidence(
|
|
101
101
|
[
|
|
102
102
|
createRuleEvidence('R-001', {
|
|
@@ -156,7 +156,7 @@ describe('internalization-routing-policy', () => {
|
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
it('recommends skill when a cheaper non-code path is viable', () => {
|
|
159
|
-
const recommendation =
|
|
159
|
+
const recommendation = recommendLifecycleRoute(
|
|
160
160
|
createPrincipleEvidence([
|
|
161
161
|
createRuleEvidence('R-001', {
|
|
162
162
|
rule: {
|
|
@@ -201,7 +201,7 @@ describe('internalization-routing-policy', () => {
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
it('recommends defer when evidence is too sparse to choose a route confidently', () => {
|
|
204
|
-
const recommendation =
|
|
204
|
+
const recommendation = recommendLifecycleRoute(
|
|
205
205
|
createPrincipleEvidence([createRuleEvidence('R-001')]),
|
|
206
206
|
);
|
|
207
207
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { FilesystemLifecycleDatasource, LineageSourceRetiredError } from '../../../src/core/principle-internalization/filesystem-lifecycle-datasource.js';
|
|
3
|
+
import { buildLifecycleReadModel } from '@principles/core/runtime-v2';
|
|
4
|
+
|
|
5
|
+
describe('LineageSourceRetiredError regression guard (PRI-230)', () => {
|
|
6
|
+
it('listLineageRecords throws LineageSourceRetiredError, not return []', () => {
|
|
7
|
+
const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
|
|
8
|
+
expect(() => ds.listLineageRecords('rule-implementation-candidate')).toThrow(LineageSourceRetiredError);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('LineageSourceRetiredError message mentions PRI-230 and nocturnal-artifact-lineage', () => {
|
|
12
|
+
const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
|
|
13
|
+
try {
|
|
14
|
+
ds.listLineageRecords('rule-implementation-candidate');
|
|
15
|
+
expect.unreachable('Should have thrown');
|
|
16
|
+
} catch (err) {
|
|
17
|
+
expect(err).toBeInstanceOf(LineageSourceRetiredError);
|
|
18
|
+
const message = (err as LineageSourceRetiredError).message;
|
|
19
|
+
expect(message).toContain('PRI-230');
|
|
20
|
+
expect(message).toContain('nocturnal-artifact-lineage');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('LineageSourceRetiredError has correct name property', () => {
|
|
25
|
+
const err = new LineageSourceRetiredError();
|
|
26
|
+
expect(err.name).toBe('LineageSourceRetiredError');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('retired datasource does not return empty array that could be misinterpreted as "no lineage"', () => {
|
|
30
|
+
const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
|
|
31
|
+
let returnedEmptyArray = false;
|
|
32
|
+
try {
|
|
33
|
+
const result = ds.listLineageRecords('rule-implementation-candidate');
|
|
34
|
+
returnedEmptyArray = Array.isArray(result) && result.length === 0;
|
|
35
|
+
} catch {
|
|
36
|
+
// expected
|
|
37
|
+
}
|
|
38
|
+
expect(returnedEmptyArray).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('buildLifecycleReadModel with retired lineage source (PRI-230)', () => {
|
|
43
|
+
it('produces lineageEvidence with sourceRetired=true when datasource throws LineageSourceRetiredError', () => {
|
|
44
|
+
const ds = new FilesystemLifecycleDatasource('/tmp/nonexistent', '/tmp/nonexistent');
|
|
45
|
+
let model: ReturnType<typeof buildLifecycleReadModel>;
|
|
46
|
+
expect(() => {
|
|
47
|
+
model = buildLifecycleReadModel(ds);
|
|
48
|
+
}).not.toThrow();
|
|
49
|
+
|
|
50
|
+
const rules = model!.principles.flatMap((p) => p.rules);
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
expect(rule.lineageEvidence.sourceRetired).toBe(true);
|
|
53
|
+
expect(rule.lineageEvidence.records).toEqual([]);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -3,7 +3,6 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { PrincipleLifecycleService } from '../../../src/core/principle-internalization/principle-lifecycle-service.js';
|
|
6
|
-
import { appendCandidateArtifactLineageRecord, listArtifactLineageRecords } from '../../../src/core/nocturnal-artifact-lineage.js';
|
|
7
6
|
import { getImplementationAssetRoot } from '../../../src/core/code-implementation-storage.js';
|
|
8
7
|
import { loadLedger, saveLedger, type LedgerPrinciple, type LedgerRule } from '../../../src/core/principle-tree-ledger.js';
|
|
9
8
|
import type { Implementation } from '../../../src/types/principle-tree-schema.js';
|
|
@@ -142,19 +141,6 @@ describe('principle-lifecycle-service', () => {
|
|
|
142
141
|
JSON.stringify(makeReplayReport(), null, 2),
|
|
143
142
|
'utf-8',
|
|
144
143
|
);
|
|
145
|
-
|
|
146
|
-
appendCandidateArtifactLineageRecord(workspaceDir, {
|
|
147
|
-
artifactId: 'artifact-1',
|
|
148
|
-
principleId: 'P-001',
|
|
149
|
-
ruleId: 'R-001',
|
|
150
|
-
sessionId: 'session-1',
|
|
151
|
-
sourceSnapshotRef: 'snapshot-1',
|
|
152
|
-
sourcePainIds: ['pain-1'],
|
|
153
|
-
sourceGateBlockIds: ['gate-1'],
|
|
154
|
-
storagePath: getImplementationAssetRoot(stateDir, 'IMPL-001'),
|
|
155
|
-
implementationId: 'IMPL-001',
|
|
156
|
-
createdAt: '2026-04-08T00:00:00.000Z',
|
|
157
|
-
});
|
|
158
144
|
}
|
|
159
145
|
|
|
160
146
|
it('recomputes lifecycle metrics and exposes assessments plus route recommendations through one service', () => {
|
|
@@ -185,13 +171,12 @@ describe('principle-lifecycle-service', () => {
|
|
|
185
171
|
expect(context.getActivePrincipleSubtrees()).toEqual([]);
|
|
186
172
|
});
|
|
187
173
|
|
|
188
|
-
it('keeps replay classifications behavioral-only
|
|
174
|
+
it('keeps replay classifications behavioral-only', () => {
|
|
189
175
|
seedWorkspace();
|
|
190
176
|
const service = new PrincipleLifecycleService(workspaceDir, stateDir);
|
|
191
177
|
|
|
192
178
|
const assessments = service.listAssessments();
|
|
193
179
|
const readModel = service.buildReadModel();
|
|
194
|
-
const lineage = listArtifactLineageRecords(workspaceDir, 'rule-implementation-candidate');
|
|
195
180
|
const latestReport = readModel.principles[0].rules[0].replayEvidence.latestReports[0];
|
|
196
181
|
|
|
197
182
|
expect(assessments[0].routeRecommendation.route).toBe('skill');
|
|
@@ -200,12 +185,5 @@ describe('principle-lifecycle-service', () => {
|
|
|
200
185
|
'successPositive',
|
|
201
186
|
'principleAnchor',
|
|
202
187
|
]);
|
|
203
|
-
expect(lineage).toHaveLength(1);
|
|
204
|
-
expect(lineage[0]).toMatchObject({
|
|
205
|
-
artifactKind: 'rule-implementation-candidate',
|
|
206
|
-
sourcePainIds: ['pain-1'],
|
|
207
|
-
sourceGateBlockIds: ['gate-1'],
|
|
208
|
-
implementationId: 'IMPL-001',
|
|
209
|
-
});
|
|
210
188
|
});
|
|
211
189
|
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { PrincipleTreeLedgerAdapter } from '../../src/core/principle-tree-ledger-adapter.js';
|
|
6
|
+
import { loadLedger } from '../../src/core/principle-tree-ledger.js';
|
|
7
|
+
import { CandidateIntakeError, INTAKE_ERROR_CODES } from '@principles/core/runtime-v2';
|
|
8
|
+
import type { LedgerPrincipleEntry } from '@principles/core/runtime-v2';
|
|
9
|
+
import { safeRmDir } from '../test-utils.js';
|
|
10
|
+
|
|
11
|
+
// Mock principle-tree-ledger.js module
|
|
12
|
+
// Use vi.mock with factory to provide controllable addPrincipleToLedger
|
|
13
|
+
vi.mock('../../src/core/principle-tree-ledger.js', async () => {
|
|
14
|
+
const actual = await vi.importActual<typeof import('../../src/core/principle-tree-ledger.js')>('../../src/core/principle-tree-ledger.js');
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
addPrincipleToLedger: vi.fn(actual.addPrincipleToLedger),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('PrincipleTreeLedgerAdapter', () => {
|
|
22
|
+
let tempDir: string;
|
|
23
|
+
let stateDir: string;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-adapter-'));
|
|
27
|
+
stateDir = path.join(tempDir, '.state');
|
|
28
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
safeRmDir(tempDir);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function createEntry(overrides: Partial<LedgerPrincipleEntry> = {}): LedgerPrincipleEntry {
|
|
36
|
+
return {
|
|
37
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
38
|
+
title: 'Test Principle',
|
|
39
|
+
text: 'When file delete is attempted, then verify backup exists first.',
|
|
40
|
+
triggerPattern: 'file delete',
|
|
41
|
+
action: 'verify backup exists',
|
|
42
|
+
status: 'probation',
|
|
43
|
+
evaluability: 'weak_heuristic',
|
|
44
|
+
sourceRef: 'candidate://test-candidate-001',
|
|
45
|
+
artifactRef: 'artifact://test-artifact-001',
|
|
46
|
+
taskRef: 'task://test-task-001',
|
|
47
|
+
createdAt: '2026-04-26T10:00:00.000Z',
|
|
48
|
+
...overrides,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('writeProbationEntry', () => {
|
|
53
|
+
it('writes a LedgerPrinciple to the ledger file with correct field expansion', () => {
|
|
54
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
55
|
+
const entry = createEntry();
|
|
56
|
+
const result = adapter.writeProbationEntry(entry);
|
|
57
|
+
|
|
58
|
+
expect(result).toBe(entry);
|
|
59
|
+
|
|
60
|
+
const store = loadLedger(stateDir);
|
|
61
|
+
const written = store.tree.principles[entry.id];
|
|
62
|
+
expect(written).toBeDefined();
|
|
63
|
+
expect(written.id).toBe(entry.id);
|
|
64
|
+
expect(written.text).toBe(entry.text);
|
|
65
|
+
expect(written.status).toBe('candidate');
|
|
66
|
+
expect(written.triggerPattern).toBe(entry.triggerPattern);
|
|
67
|
+
expect(written.action).toBe(entry.action);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('is idempotent — second call with same candidateId returns existing entry, no double write', () => {
|
|
71
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
72
|
+
const entry = createEntry();
|
|
73
|
+
const first = adapter.writeProbationEntry(entry);
|
|
74
|
+
const second = adapter.writeProbationEntry(entry);
|
|
75
|
+
|
|
76
|
+
expect(second).toBe(first);
|
|
77
|
+
|
|
78
|
+
const store = loadLedger(stateDir);
|
|
79
|
+
expect(Object.keys(store.tree.principles).length).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('different candidates produce different entries', () => {
|
|
83
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
84
|
+
const entry1 = createEntry({
|
|
85
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
86
|
+
sourceRef: 'candidate://alpha',
|
|
87
|
+
});
|
|
88
|
+
const entry2 = createEntry({
|
|
89
|
+
id: '660e8400-e29b-41d4-a716-446655440001',
|
|
90
|
+
sourceRef: 'candidate://beta',
|
|
91
|
+
title: 'Another Principle',
|
|
92
|
+
text: 'When network fails, retry with backoff.',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
adapter.writeProbationEntry(entry1);
|
|
96
|
+
adapter.writeProbationEntry(entry2);
|
|
97
|
+
|
|
98
|
+
const store = loadLedger(stateDir);
|
|
99
|
+
expect(Object.keys(store.tree.principles).length).toBe(2);
|
|
100
|
+
expect(store.tree.principles[entry1.id]).toBeDefined();
|
|
101
|
+
expect(store.tree.principles[entry2.id]).toBeDefined();
|
|
102
|
+
|
|
103
|
+
expect(adapter.existsForCandidate('alpha')).toBeDefined();
|
|
104
|
+
expect(adapter.existsForCandidate('beta')).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('existsForCandidate', () => {
|
|
109
|
+
it('returns the entry for a previously written candidate', () => {
|
|
110
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
111
|
+
const entry = createEntry();
|
|
112
|
+
adapter.writeProbationEntry(entry);
|
|
113
|
+
|
|
114
|
+
const found = adapter.existsForCandidate('test-candidate-001');
|
|
115
|
+
expect(found).not.toBeNull();
|
|
116
|
+
expect(found?.id).toBe(entry.id);
|
|
117
|
+
expect(found?.title).toBe(entry.title);
|
|
118
|
+
expect(found?.sourceRef).toBe(entry.sourceRef);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns null for an unknown candidate', () => {
|
|
122
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
123
|
+
const result = adapter.existsForCandidate('nonexistent-candidate');
|
|
124
|
+
expect(result).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('field expansion', () => {
|
|
129
|
+
it('maps status probation to candidate', () => {
|
|
130
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
131
|
+
const entry = createEntry();
|
|
132
|
+
adapter.writeProbationEntry(entry);
|
|
133
|
+
|
|
134
|
+
const store = loadLedger(stateDir);
|
|
135
|
+
const written = store.tree.principles[entry.id];
|
|
136
|
+
expect(written.status).toBe('candidate');
|
|
137
|
+
expect(written.status).not.toBe('probation');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('applies default values correctly', () => {
|
|
141
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
142
|
+
const entry = createEntry({
|
|
143
|
+
triggerPattern: undefined,
|
|
144
|
+
action: undefined,
|
|
145
|
+
artifactRef: undefined,
|
|
146
|
+
taskRef: undefined,
|
|
147
|
+
});
|
|
148
|
+
adapter.writeProbationEntry(entry);
|
|
149
|
+
|
|
150
|
+
const store = loadLedger(stateDir);
|
|
151
|
+
const written = store.tree.principles[entry.id];
|
|
152
|
+
expect(written.version).toBe(1);
|
|
153
|
+
expect(written.priority).toBe('P1');
|
|
154
|
+
expect(written.scope).toBe('general');
|
|
155
|
+
expect(written.valueScore).toBe(0);
|
|
156
|
+
expect(written.adherenceRate).toBe(0);
|
|
157
|
+
expect(written.painPreventedCount).toBe(0);
|
|
158
|
+
expect(written.createdAt).toBe(entry.createdAt);
|
|
159
|
+
expect(written.updatedAt).toBe(entry.createdAt);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('passes through triggerPattern and action (Decision B)', () => {
|
|
163
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
164
|
+
const entry1 = createEntry({ triggerPattern: 'file delete', action: 'verify backup' });
|
|
165
|
+
adapter.writeProbationEntry(entry1);
|
|
166
|
+
|
|
167
|
+
let store = loadLedger(stateDir);
|
|
168
|
+
let written = store.tree.principles[entry1.id];
|
|
169
|
+
expect(written.triggerPattern).toBe('file delete');
|
|
170
|
+
expect(written.action).toBe('verify backup');
|
|
171
|
+
|
|
172
|
+
const entry2 = createEntry({
|
|
173
|
+
id: '770e8400-e29b-41d4-a716-446655440002',
|
|
174
|
+
sourceRef: 'candidate://test-002',
|
|
175
|
+
triggerPattern: undefined,
|
|
176
|
+
action: undefined,
|
|
177
|
+
});
|
|
178
|
+
adapter.writeProbationEntry(entry2);
|
|
179
|
+
|
|
180
|
+
store = loadLedger(stateDir);
|
|
181
|
+
written = store.tree.principles[entry2.id];
|
|
182
|
+
expect(written.triggerPattern).toBe('');
|
|
183
|
+
expect(written.action).toBe('');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('does NOT include sourceRef, artifactRef, taskRef in ledger (Decision C)', () => {
|
|
187
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
188
|
+
const entry = createEntry();
|
|
189
|
+
adapter.writeProbationEntry(entry);
|
|
190
|
+
|
|
191
|
+
const store = loadLedger(stateDir);
|
|
192
|
+
const written = store.tree.principles[entry.id];
|
|
193
|
+
expect('sourceRef' in written).toBe(false);
|
|
194
|
+
expect('artifactRef' in written).toBe(false);
|
|
195
|
+
expect('taskRef' in written).toBe(false);
|
|
196
|
+
|
|
197
|
+
// But existsForCandidate still works
|
|
198
|
+
expect(adapter.existsForCandidate('test-candidate-001')).not.toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('populates derivedFromPainIds with candidateId (Q1 resolved)', () => {
|
|
202
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
203
|
+
const entry = createEntry({ sourceRef: 'candidate://test-candidate-001' });
|
|
204
|
+
adapter.writeProbationEntry(entry);
|
|
205
|
+
|
|
206
|
+
const store = loadLedger(stateDir);
|
|
207
|
+
const written = store.tree.principles[entry.id];
|
|
208
|
+
expect(written.derivedFromPainIds).toEqual(['test-candidate-001']);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('error handling', () => {
|
|
213
|
+
it('throws LEDGER_WRITE_FAILED when addPrincipleToLedger fails', async () => {
|
|
214
|
+
const mockedModule = await import('../../src/core/principle-tree-ledger.js');
|
|
215
|
+
const mockAdd = vi.mocked(mockedModule.addPrincipleToLedger);
|
|
216
|
+
mockAdd.mockImplementation(() => {
|
|
217
|
+
throw new Error('Disk full');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const adapter = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
221
|
+
const entry = createEntry();
|
|
222
|
+
|
|
223
|
+
expect(() => adapter.writeProbationEntry(entry)).toThrow(CandidateIntakeError);
|
|
224
|
+
try {
|
|
225
|
+
adapter.writeProbationEntry(entry);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
expect(err).toBeInstanceOf(CandidateIntakeError);
|
|
228
|
+
expect((err as CandidateIntakeError).code).toBe(INTAKE_ERROR_CODES.LEDGER_WRITE_FAILED);
|
|
229
|
+
expect((err as Error).message).toContain(entry.id);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
mockAdd.mockRestore();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('instance isolation', () => {
|
|
237
|
+
it('separate instances have separate Maps', () => {
|
|
238
|
+
const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-adapter-'));
|
|
239
|
+
const stateDir2 = path.join(tempDir2, '.state');
|
|
240
|
+
fs.mkdirSync(stateDir2, { recursive: true });
|
|
241
|
+
|
|
242
|
+
const adapterA = new PrincipleTreeLedgerAdapter({ stateDir });
|
|
243
|
+
const adapterB = new PrincipleTreeLedgerAdapter({ stateDir: stateDir2 });
|
|
244
|
+
|
|
245
|
+
const entryA = createEntry({ sourceRef: 'candidate://candidate-A' });
|
|
246
|
+
adapterA.writeProbationEntry(entryA);
|
|
247
|
+
|
|
248
|
+
expect(adapterB.existsForCandidate('candidate-A')).toBeNull();
|
|
249
|
+
|
|
250
|
+
safeRmDir(tempDir2);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -139,8 +139,6 @@ describe('ReflectionContextCollector', () => {
|
|
|
139
139
|
expect(result!.principle.id).toBe('P_001');
|
|
140
140
|
expect(result!.painEvents).toEqual([]);
|
|
141
141
|
expect(result!.sessionSnapshot).toBeNull();
|
|
142
|
-
expect(result!.lineage.sourcePainIds).toEqual(['pain_no_exist']);
|
|
143
|
-
expect(result!.lineage.sessionId).toBeNull();
|
|
144
142
|
});
|
|
145
143
|
|
|
146
144
|
it('returns context with pain events and null snapshot when painIds match events but not a specific session', () => {
|
|
@@ -159,9 +157,6 @@ describe('ReflectionContextCollector', () => {
|
|
|
159
157
|
|
|
160
158
|
expect(result).not.toBeNull();
|
|
161
159
|
expect(result!.principle.id).toBe('P_002');
|
|
162
|
-
expect(result!.lineage.sourcePainIds).toEqual(['pain_001']);
|
|
163
|
-
// painId -> sessionId resolution is a known gap, so painEvents may be empty
|
|
164
|
-
// and sessionSnapshot may be null
|
|
165
160
|
});
|
|
166
161
|
|
|
167
162
|
it('resolves pain events from a known session', () => {
|
|
@@ -181,9 +176,6 @@ describe('ReflectionContextCollector', () => {
|
|
|
181
176
|
|
|
182
177
|
expect(result).not.toBeNull();
|
|
183
178
|
expect(result!.principle.id).toBe('P_003');
|
|
184
|
-
// painId -> sessionId gap: we attempt to find sessions containing pain events
|
|
185
|
-
// The lineage sessionId should be populated if we can resolve it
|
|
186
|
-
expect(result!.lineage.sourcePainIds).toEqual(['pain_from_sess_with_pain']);
|
|
187
179
|
});
|
|
188
180
|
});
|
|
189
181
|
|
|
@@ -258,8 +250,6 @@ describe('ReflectionContextCollector', () => {
|
|
|
258
250
|
expect(results).toHaveLength(1);
|
|
259
251
|
|
|
260
252
|
const ctx = results[0];
|
|
261
|
-
expect(ctx.lineage.sourcePainIds).toEqual(['p1', 'p2', 'p3']);
|
|
262
|
-
expect(ctx.lineage.sessionId).toBeNull();
|
|
263
253
|
expect(ctx.sessionSnapshot).toBeNull();
|
|
264
254
|
});
|
|
265
255
|
});
|
|
@@ -297,7 +287,6 @@ describe('ReflectionContextCollector', () => {
|
|
|
297
287
|
// Exact match: only ONE pain event should match
|
|
298
288
|
expect(result!.painEvents).toHaveLength(1);
|
|
299
289
|
expect(result!.painEvents[0].reason).toBe('exact_match_target');
|
|
300
|
-
expect(result!.lineage.sessionId).toBe(sessionId);
|
|
301
290
|
});
|
|
302
291
|
|
|
303
292
|
it('falls back to substring heuristic when no exact ID match', () => {
|
|
@@ -340,17 +329,14 @@ describe('ReflectionContextCollector', () => {
|
|
|
340
329
|
expect(results).toHaveLength(1);
|
|
341
330
|
|
|
342
331
|
const ctx: ReflectionContext = results[0];
|
|
343
|
-
// Verify all fields exist
|
|
344
332
|
expect(ctx).toHaveProperty('principle');
|
|
345
333
|
expect(ctx).toHaveProperty('painEvents');
|
|
346
334
|
expect(ctx).toHaveProperty('sessionSnapshot');
|
|
347
|
-
expect(ctx).toHaveProperty('lineage');
|
|
348
335
|
|
|
349
336
|
expect(ctx.principle.id).toBe('P_SHAPE');
|
|
350
337
|
expect(ctx.principle.text).toBe('Shape test principle');
|
|
351
338
|
expect(ctx.principle.action).toBe('Do the thing');
|
|
352
339
|
expect(Array.isArray(ctx.painEvents)).toBe(true);
|
|
353
|
-
expect(ctx.lineage.sourcePainIds).toEqual(['pain_shape']);
|
|
354
340
|
});
|
|
355
341
|
});
|
|
356
342
|
});
|