@soleri/core 2.1.0 → 2.5.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/dist/brain/brain.d.ts +10 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +116 -13
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +36 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +119 -14
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +34 -2
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/cognee/client.d.ts +3 -0
- package/dist/cognee/client.d.ts.map +1 -1
- package/dist/cognee/client.js +17 -0
- package/dist/cognee/client.js.map +1 -1
- package/dist/cognee/sync-manager.d.ts +94 -0
- package/dist/cognee/sync-manager.d.ts.map +1 -0
- package/dist/cognee/sync-manager.js +293 -0
- package/dist/cognee/sync-manager.js.map +1 -0
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +37 -1
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +199 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/errors/classify.d.ts +13 -0
- package/dist/errors/classify.d.ts.map +1 -0
- package/dist/errors/classify.js +97 -0
- package/dist/errors/classify.js.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/retry.d.ts +40 -0
- package/dist/errors/retry.d.ts.map +1 -0
- package/dist/errors/retry.js +97 -0
- package/dist/errors/retry.js.map +1 -0
- package/dist/errors/types.d.ts +48 -0
- package/dist/errors/types.d.ts.map +1 -0
- package/dist/errors/types.js +59 -0
- package/dist/errors/types.js.map +1 -0
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +52 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -1
- package/dist/index.js.map +1 -1
- package/dist/intake/content-classifier.d.ts +14 -0
- package/dist/intake/content-classifier.d.ts.map +1 -0
- package/dist/intake/content-classifier.js +125 -0
- package/dist/intake/content-classifier.js.map +1 -0
- package/dist/intake/dedup-gate.d.ts +17 -0
- package/dist/intake/dedup-gate.d.ts.map +1 -0
- package/dist/intake/dedup-gate.js +66 -0
- package/dist/intake/dedup-gate.js.map +1 -0
- package/dist/intake/intake-pipeline.d.ts +63 -0
- package/dist/intake/intake-pipeline.d.ts.map +1 -0
- package/dist/intake/intake-pipeline.js +373 -0
- package/dist/intake/intake-pipeline.js.map +1 -0
- package/dist/intake/types.d.ts +65 -0
- package/dist/intake/types.d.ts.map +1 -0
- package/dist/intake/types.js +3 -0
- package/dist/intake/types.js.map +1 -0
- package/dist/intelligence/loader.js +1 -1
- package/dist/intelligence/loader.js.map +1 -1
- package/dist/intelligence/types.d.ts +3 -1
- package/dist/intelligence/types.d.ts.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +100 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +379 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +103 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +11 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/persistence/index.d.ts +3 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +2 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/sqlite-provider.d.ts +25 -0
- package/dist/persistence/sqlite-provider.d.ts.map +1 -0
- package/dist/persistence/sqlite-provider.js +59 -0
- package/dist/persistence/sqlite-provider.js.map +1 -0
- package/dist/persistence/types.d.ts +36 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +8 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +72 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +442 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +421 -4
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +949 -21
- package/dist/planning/planner.js.map +1 -1
- package/dist/playbooks/generic/brainstorming.d.ts +9 -0
- package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
- package/dist/playbooks/generic/brainstorming.js +105 -0
- package/dist/playbooks/generic/brainstorming.js.map +1 -0
- package/dist/playbooks/generic/code-review.d.ts +11 -0
- package/dist/playbooks/generic/code-review.d.ts.map +1 -0
- package/dist/playbooks/generic/code-review.js +176 -0
- package/dist/playbooks/generic/code-review.js.map +1 -0
- package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
- package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
- package/dist/playbooks/generic/subagent-execution.js +68 -0
- package/dist/playbooks/generic/subagent-execution.js.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
- package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
- package/dist/playbooks/generic/systematic-debugging.js +87 -0
- package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
- package/dist/playbooks/generic/tdd.d.ts +9 -0
- package/dist/playbooks/generic/tdd.d.ts.map +1 -0
- package/dist/playbooks/generic/tdd.js +70 -0
- package/dist/playbooks/generic/tdd.js.map +1 -0
- package/dist/playbooks/generic/verification.d.ts +9 -0
- package/dist/playbooks/generic/verification.d.ts.map +1 -0
- package/dist/playbooks/generic/verification.js +74 -0
- package/dist/playbooks/generic/verification.js.map +1 -0
- package/dist/playbooks/index.d.ts +4 -0
- package/dist/playbooks/index.d.ts.map +1 -0
- package/dist/playbooks/index.js +5 -0
- package/dist/playbooks/index.js.map +1 -0
- package/dist/playbooks/playbook-registry.d.ts +42 -0
- package/dist/playbooks/playbook-registry.d.ts.map +1 -0
- package/dist/playbooks/playbook-registry.js +227 -0
- package/dist/playbooks/playbook-registry.js.map +1 -0
- package/dist/playbooks/playbook-seeder.d.ts +47 -0
- package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
- package/dist/playbooks/playbook-seeder.js +104 -0
- package/dist/playbooks/playbook-seeder.js.map +1 -0
- package/dist/playbooks/playbook-types.d.ts +132 -0
- package/dist/playbooks/playbook-types.d.ts.map +1 -0
- package/dist/playbooks/playbook-types.js +12 -0
- package/dist/playbooks/playbook-types.js.map +1 -0
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +274 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/prompts/index.d.ts +4 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/parser.d.ts +17 -0
- package/dist/prompts/parser.d.ts.map +1 -0
- package/dist/prompts/parser.js +47 -0
- package/dist/prompts/parser.js.map +1 -0
- package/dist/prompts/template-manager.d.ts +25 -0
- package/dist/prompts/template-manager.d.ts.map +1 -0
- package/dist/prompts/template-manager.js +71 -0
- package/dist/prompts/template-manager.js.map +1 -0
- package/dist/prompts/types.d.ts +26 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +5 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +15 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +595 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +329 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +363 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/cognee-sync-ops.d.ts +12 -0
- package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
- package/dist/runtime/cognee-sync-ops.js +55 -0
- package/dist/runtime/cognee-sync-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +9 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +693 -10
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +71 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +61 -15
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/intake-ops.d.ts +14 -0
- package/dist/runtime/intake-ops.d.ts.map +1 -0
- package/dist/runtime/intake-ops.js +110 -0
- package/dist/runtime/intake-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +14 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +251 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +246 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +25 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +663 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/playbook-ops.d.ts +14 -0
- package/dist/runtime/playbook-ops.d.ts.map +1 -0
- package/dist/runtime/playbook-ops.js +141 -0
- package/dist/runtime/playbook-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +186 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +65 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +29 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +10 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +536 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/playbook.d.ts +34 -0
- package/dist/vault/playbook.d.ts.map +1 -0
- package/dist/vault/playbook.js +60 -0
- package/dist/vault/playbook.js.map +1 -0
- package/dist/vault/vault.d.ts +97 -4
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +424 -65
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -3
- package/src/__tests__/admin-extra-ops.test.ts +467 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +205 -0
- package/src/__tests__/brain.test.ts +134 -3
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/cognee-integration.test.ts +80 -0
- package/src/__tests__/cognee-sync-manager.test.ts +103 -0
- package/src/__tests__/core-ops.test.ts +292 -2
- package/src/__tests__/curator-extra-ops.test.ts +381 -0
- package/src/__tests__/domain-ops.test.ts +66 -0
- package/src/__tests__/errors.test.ts +388 -0
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +361 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intake-pipeline.test.ts +162 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +469 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +248 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +289 -0
- package/src/__tests__/persistence.test.ts +225 -0
- package/src/__tests__/planner.test.ts +416 -7
- package/src/__tests__/planning-extra-ops.test.ts +706 -0
- package/src/__tests__/playbook-registry.test.ts +326 -0
- package/src/__tests__/playbook-seeder.test.ts +163 -0
- package/src/__tests__/playbook.test.ts +389 -0
- package/src/__tests__/project-ops.test.ts +381 -0
- package/src/__tests__/template-manager.test.ts +222 -0
- package/src/__tests__/vault-extra-ops.test.ts +482 -0
- package/src/brain/brain.ts +185 -16
- package/src/brain/intelligence.ts +179 -10
- package/src/brain/types.ts +40 -2
- package/src/cognee/client.ts +18 -0
- package/src/cognee/sync-manager.ts +389 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +295 -1
- package/src/errors/classify.ts +102 -0
- package/src/errors/index.ts +5 -0
- package/src/errors/retry.ts +132 -0
- package/src/errors/types.ts +81 -0
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +213 -2
- package/src/intake/content-classifier.ts +146 -0
- package/src/intake/dedup-gate.ts +92 -0
- package/src/intake/intake-pipeline.ts +503 -0
- package/src/intake/types.ts +69 -0
- package/src/intelligence/loader.ts +1 -1
- package/src/intelligence/types.ts +3 -1
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +448 -0
- package/src/loop/types.ts +115 -0
- package/src/persistence/index.ts +7 -0
- package/src/persistence/sqlite-provider.ts +62 -0
- package/src/persistence/types.ts +44 -0
- package/src/planning/gap-analysis.ts +775 -0
- package/src/planning/gap-types.ts +61 -0
- package/src/planning/planner.ts +1273 -24
- package/src/playbooks/generic/brainstorming.ts +110 -0
- package/src/playbooks/generic/code-review.ts +181 -0
- package/src/playbooks/generic/subagent-execution.ts +74 -0
- package/src/playbooks/generic/systematic-debugging.ts +92 -0
- package/src/playbooks/generic/tdd.ts +75 -0
- package/src/playbooks/generic/verification.ts +79 -0
- package/src/playbooks/index.ts +27 -0
- package/src/playbooks/playbook-registry.ts +284 -0
- package/src/playbooks/playbook-seeder.ts +119 -0
- package/src/playbooks/playbook-types.ts +162 -0
- package/src/project/project-registry.ts +370 -0
- package/src/project/types.ts +31 -0
- package/src/prompts/index.ts +3 -0
- package/src/prompts/parser.ts +59 -0
- package/src/prompts/template-manager.ts +77 -0
- package/src/prompts/types.ts +28 -0
- package/src/runtime/admin-extra-ops.ts +652 -0
- package/src/runtime/admin-ops.ts +340 -0
- package/src/runtime/capture-ops.ts +404 -0
- package/src/runtime/cognee-sync-ops.ts +63 -0
- package/src/runtime/core-ops.ts +787 -9
- package/src/runtime/curator-extra-ops.ts +85 -0
- package/src/runtime/domain-ops.ts +67 -15
- package/src/runtime/grading-ops.ts +130 -0
- package/src/runtime/intake-ops.ts +126 -0
- package/src/runtime/loop-ops.ts +277 -0
- package/src/runtime/memory-cross-project-ops.ts +191 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +278 -0
- package/src/runtime/planning-extra-ops.ts +718 -0
- package/src/runtime/playbook-ops.ts +169 -0
- package/src/runtime/project-ops.ts +202 -0
- package/src/runtime/runtime.ts +77 -3
- package/src/runtime/types.ts +29 -0
- package/src/runtime/vault-extra-ops.ts +606 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/playbook.ts +87 -0
- package/src/vault/vault.ts +575 -98
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SoleriErrorCode,
|
|
4
|
+
SoleriError,
|
|
5
|
+
ok,
|
|
6
|
+
err,
|
|
7
|
+
isOk,
|
|
8
|
+
isErr,
|
|
9
|
+
classifyError,
|
|
10
|
+
shouldRetry,
|
|
11
|
+
getRetryDelay,
|
|
12
|
+
retryWithPreset,
|
|
13
|
+
RETRY_PRESETS,
|
|
14
|
+
} from '../errors/index.js';
|
|
15
|
+
|
|
16
|
+
// ─── SoleriError ──────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
describe('SoleriError', () => {
|
|
19
|
+
it('classifies RATE_LIMIT as retryable', () => {
|
|
20
|
+
const e = new SoleriError('rate limited', SoleriErrorCode.RATE_LIMIT);
|
|
21
|
+
expect(e.code).toBe(SoleriErrorCode.RATE_LIMIT);
|
|
22
|
+
expect(e.classification).toBe('retryable');
|
|
23
|
+
expect(e.retryable).toBe(true);
|
|
24
|
+
expect(e.name).toBe('SoleriError');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('classifies AUTH as permanent', () => {
|
|
28
|
+
const e = new SoleriError('unauthorized', SoleriErrorCode.AUTH);
|
|
29
|
+
expect(e.classification).toBe('permanent');
|
|
30
|
+
expect(e.retryable).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('classifies VALIDATION as fixable', () => {
|
|
34
|
+
const e = new SoleriError('bad input', SoleriErrorCode.VALIDATION);
|
|
35
|
+
expect(e.classification).toBe('fixable');
|
|
36
|
+
expect(e.retryable).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('classifies NETWORK as retryable', () => {
|
|
40
|
+
const e = new SoleriError('connection refused', SoleriErrorCode.NETWORK);
|
|
41
|
+
expect(e.classification).toBe('retryable');
|
|
42
|
+
expect(e.retryable).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('classifies RESOURCE_NOT_FOUND as permanent', () => {
|
|
46
|
+
const e = new SoleriError('not found', SoleriErrorCode.RESOURCE_NOT_FOUND);
|
|
47
|
+
expect(e.classification).toBe('permanent');
|
|
48
|
+
expect(e.retryable).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('classifies CONFIG_ERROR as permanent', () => {
|
|
52
|
+
const e = new SoleriError('missing key', SoleriErrorCode.CONFIG_ERROR);
|
|
53
|
+
expect(e.classification).toBe('permanent');
|
|
54
|
+
expect(e.retryable).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('preserves cause and context', () => {
|
|
58
|
+
const cause = new Error('original');
|
|
59
|
+
const e = new SoleriError('wrapped', SoleriErrorCode.INTERNAL, {
|
|
60
|
+
cause,
|
|
61
|
+
context: { attempt: 3, url: 'http://example.com' },
|
|
62
|
+
});
|
|
63
|
+
expect(e.cause).toBe(cause);
|
|
64
|
+
expect(e.context).toEqual({ attempt: 3, url: 'http://example.com' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('extends Error', () => {
|
|
68
|
+
const e = new SoleriError('test', SoleriErrorCode.INTERNAL);
|
|
69
|
+
expect(e).toBeInstanceOf(Error);
|
|
70
|
+
expect(e).toBeInstanceOf(SoleriError);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ─── classifyError ────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
describe('classifyError', () => {
|
|
77
|
+
it('passes through SoleriError unchanged', () => {
|
|
78
|
+
const original = new SoleriError('test', SoleriErrorCode.AUTH);
|
|
79
|
+
expect(classifyError(original)).toBe(original);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('classifies HTTP 429 as RATE_LIMIT', () => {
|
|
83
|
+
const e = classifyError({ status: 429, message: 'Too many requests' });
|
|
84
|
+
expect(e.code).toBe(SoleriErrorCode.RATE_LIMIT);
|
|
85
|
+
expect(e.retryable).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('classifies HTTP 401 as AUTH', () => {
|
|
89
|
+
const e = classifyError({ status: 401, message: 'Unauthorized' });
|
|
90
|
+
expect(e.code).toBe(SoleriErrorCode.AUTH);
|
|
91
|
+
expect(e.retryable).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('classifies HTTP 403 as AUTH', () => {
|
|
95
|
+
const e = classifyError({ status: 403, message: 'Forbidden' });
|
|
96
|
+
expect(e.code).toBe(SoleriErrorCode.AUTH);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('classifies HTTP 404 as RESOURCE_NOT_FOUND', () => {
|
|
100
|
+
const e = classifyError({ status: 404, message: 'Not Found' });
|
|
101
|
+
expect(e.code).toBe(SoleriErrorCode.RESOURCE_NOT_FOUND);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('classifies HTTP 503 as INTERNAL (retryable)', () => {
|
|
105
|
+
const e = classifyError({ status: 503, message: 'Service Unavailable' });
|
|
106
|
+
expect(e.code).toBe(SoleriErrorCode.INTERNAL);
|
|
107
|
+
expect(e.retryable).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('classifies HTTP 408 as TIMEOUT', () => {
|
|
111
|
+
const e = classifyError({ status: 408, message: 'Request Timeout' });
|
|
112
|
+
expect(e.code).toBe(SoleriErrorCode.TIMEOUT);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('classifies HTTP 422 as VALIDATION', () => {
|
|
116
|
+
const e = classifyError({ status: 422, message: 'Unprocessable Entity' });
|
|
117
|
+
expect(e.code).toBe(SoleriErrorCode.VALIDATION);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('classifies ECONNREFUSED as NETWORK', () => {
|
|
121
|
+
const error = new Error('connect ECONNREFUSED');
|
|
122
|
+
(error as unknown as Record<string, string>).code = 'ECONNREFUSED';
|
|
123
|
+
const e = classifyError(error);
|
|
124
|
+
expect(e.code).toBe(SoleriErrorCode.NETWORK);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('classifies ETIMEDOUT as TIMEOUT', () => {
|
|
128
|
+
const error = new Error('connect ETIMEDOUT');
|
|
129
|
+
(error as unknown as Record<string, string>).code = 'ETIMEDOUT';
|
|
130
|
+
const e = classifyError(error);
|
|
131
|
+
expect(e.code).toBe(SoleriErrorCode.TIMEOUT);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('classifies "model overloaded" message as LLM_OVERLOAD', () => {
|
|
135
|
+
const e = classifyError(new Error('model overloaded, please retry'));
|
|
136
|
+
expect(e.code).toBe(SoleriErrorCode.LLM_OVERLOAD);
|
|
137
|
+
expect(e.retryable).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('classifies "capacity" message as LLM_OVERLOAD', () => {
|
|
141
|
+
const e = classifyError(new Error('server at capacity'));
|
|
142
|
+
expect(e.code).toBe(SoleriErrorCode.LLM_OVERLOAD);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('classifies "vault" message as VAULT_UNREACHABLE', () => {
|
|
146
|
+
const e = classifyError(new Error('vault connection lost'));
|
|
147
|
+
expect(e.code).toBe(SoleriErrorCode.VAULT_UNREACHABLE);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('classifies "invalid" message as VALIDATION', () => {
|
|
151
|
+
const e = classifyError(new Error('invalid input format'));
|
|
152
|
+
expect(e.code).toBe(SoleriErrorCode.VALIDATION);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('classifies "configuration" message as CONFIG_ERROR', () => {
|
|
156
|
+
const e = classifyError(new Error('missing configuration'));
|
|
157
|
+
expect(e.code).toBe(SoleriErrorCode.CONFIG_ERROR);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('defaults unknown error to INTERNAL (permanent)', () => {
|
|
161
|
+
const e = classifyError(new Error('something weird happened'));
|
|
162
|
+
expect(e.code).toBe(SoleriErrorCode.INTERNAL);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('handles string input', () => {
|
|
166
|
+
const e = classifyError('plain string error');
|
|
167
|
+
expect(e).toBeInstanceOf(SoleriError);
|
|
168
|
+
expect(e.message).toBe('plain string error');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('handles null input', () => {
|
|
172
|
+
const e = classifyError(null);
|
|
173
|
+
expect(e).toBeInstanceOf(SoleriError);
|
|
174
|
+
expect(e.message).toBe('null');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('handles undefined input', () => {
|
|
178
|
+
const e = classifyError(undefined);
|
|
179
|
+
expect(e).toBeInstanceOf(SoleriError);
|
|
180
|
+
expect(e.message).toBe('undefined');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('prefers HTTP status over message pattern', () => {
|
|
184
|
+
// status=401 should win over "invalid" in message
|
|
185
|
+
const e = classifyError({ status: 401, message: 'invalid credentials' });
|
|
186
|
+
expect(e.code).toBe(SoleriErrorCode.AUTH);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('preserves original error as cause', () => {
|
|
190
|
+
const original = new Error('original');
|
|
191
|
+
const e = classifyError(original);
|
|
192
|
+
expect(e.cause).toBe(original);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('uses statusCode if status is absent', () => {
|
|
196
|
+
const e = classifyError({ statusCode: 429, message: 'rate limited' });
|
|
197
|
+
expect(e.code).toBe(SoleriErrorCode.RATE_LIMIT);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ─── shouldRetry ──────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
describe('shouldRetry', () => {
|
|
204
|
+
it('returns true for retryable error below max attempts', () => {
|
|
205
|
+
const e = new SoleriError('net', SoleriErrorCode.NETWORK);
|
|
206
|
+
expect(shouldRetry(e, 1, 'fast')).toBe(true); // max=3, attempt=1
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('returns false for retryable error at max attempts', () => {
|
|
210
|
+
const e = new SoleriError('net', SoleriErrorCode.NETWORK);
|
|
211
|
+
expect(shouldRetry(e, 3, 'fast')).toBe(false); // max=3, attempt=3
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('returns false for permanent error regardless', () => {
|
|
215
|
+
const e = new SoleriError('auth', SoleriErrorCode.AUTH);
|
|
216
|
+
expect(shouldRetry(e, 0, 'patient')).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('returns false for fixable error regardless', () => {
|
|
220
|
+
const e = new SoleriError('val', SoleriErrorCode.VALIDATION);
|
|
221
|
+
expect(shouldRetry(e, 0, 'normal')).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ─── getRetryDelay ────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
describe('getRetryDelay', () => {
|
|
228
|
+
it('increases with attempt number', () => {
|
|
229
|
+
const delays = Array.from({ length: 5 }, (_, i) => getRetryDelay(i, 'normal'));
|
|
230
|
+
// Check trend is generally increasing (jitter may cause minor inversions)
|
|
231
|
+
const avg0 = getAvgDelay(0, 'normal');
|
|
232
|
+
const avg2 = getAvgDelay(2, 'normal');
|
|
233
|
+
expect(avg2).toBeGreaterThan(avg0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('caps at maxInterval', () => {
|
|
237
|
+
const delay = getRetryDelay(100, 'fast');
|
|
238
|
+
// maxInterval for fast is 10_000, with 25% jitter max = 12_500
|
|
239
|
+
expect(delay).toBeLessThanOrEqual(12_500);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('returns non-negative values', () => {
|
|
243
|
+
for (let i = 0; i < 50; i++) {
|
|
244
|
+
expect(getRetryDelay(i, 'patient')).toBeGreaterThanOrEqual(0);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Average over multiple calls to smooth jitter
|
|
250
|
+
function getAvgDelay(attempt: number, preset: 'fast' | 'normal' | 'patient'): number {
|
|
251
|
+
let sum = 0;
|
|
252
|
+
const runs = 100;
|
|
253
|
+
for (let i = 0; i < runs; i++) sum += getRetryDelay(attempt, preset);
|
|
254
|
+
return sum / runs;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── RETRY_PRESETS ────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
describe('RETRY_PRESETS', () => {
|
|
260
|
+
it('has fast preset (1s/10s/3)', () => {
|
|
261
|
+
expect(RETRY_PRESETS.fast).toEqual({
|
|
262
|
+
initialIntervalMs: 1_000,
|
|
263
|
+
maxIntervalMs: 10_000,
|
|
264
|
+
maxAttempts: 3,
|
|
265
|
+
backoffMultiplier: 2,
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('has normal preset (10s/2min/10)', () => {
|
|
270
|
+
expect(RETRY_PRESETS.normal).toEqual({
|
|
271
|
+
initialIntervalMs: 10_000,
|
|
272
|
+
maxIntervalMs: 120_000,
|
|
273
|
+
maxAttempts: 10,
|
|
274
|
+
backoffMultiplier: 2,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('has patient preset (1min/15min/25)', () => {
|
|
279
|
+
expect(RETRY_PRESETS.patient).toEqual({
|
|
280
|
+
initialIntervalMs: 60_000,
|
|
281
|
+
maxIntervalMs: 900_000,
|
|
282
|
+
maxAttempts: 25,
|
|
283
|
+
backoffMultiplier: 1.5,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ─── retryWithPreset ──────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
describe('retryWithPreset', () => {
|
|
291
|
+
it('succeeds on first try', async () => {
|
|
292
|
+
const result = await retryWithPreset(() => Promise.resolve(42), 'fast');
|
|
293
|
+
expect(result).toEqual({ ok: true, value: 42 });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('retries and eventually succeeds', async () => {
|
|
297
|
+
let calls = 0;
|
|
298
|
+
const fn = async () => {
|
|
299
|
+
calls++;
|
|
300
|
+
if (calls < 3) throw Object.assign(new Error('timeout'), { code: 'ETIMEDOUT' });
|
|
301
|
+
return 'done';
|
|
302
|
+
};
|
|
303
|
+
const result = await retryWithPreset(fn, 'fast', {
|
|
304
|
+
onRetry: vi.fn(),
|
|
305
|
+
});
|
|
306
|
+
expect(result).toEqual({ ok: true, value: 'done' });
|
|
307
|
+
expect(calls).toBe(3);
|
|
308
|
+
}, 30_000);
|
|
309
|
+
|
|
310
|
+
it('returns err immediately for permanent error', async () => {
|
|
311
|
+
const fn = async () => {
|
|
312
|
+
throw Object.assign(new Error('forbidden'), { status: 403 });
|
|
313
|
+
};
|
|
314
|
+
const result = await retryWithPreset(fn, 'fast');
|
|
315
|
+
expect(result.ok).toBe(false);
|
|
316
|
+
if (!result.ok) {
|
|
317
|
+
expect(result.error.code).toBe(SoleriErrorCode.AUTH);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('returns err after exhausting max attempts', async () => {
|
|
322
|
+
let calls = 0;
|
|
323
|
+
const fn = async () => {
|
|
324
|
+
calls++;
|
|
325
|
+
throw Object.assign(new Error('timeout'), { code: 'ETIMEDOUT' });
|
|
326
|
+
};
|
|
327
|
+
const result = await retryWithPreset(fn, 'fast');
|
|
328
|
+
expect(result.ok).toBe(false);
|
|
329
|
+
expect(calls).toBe(3); // fast preset has maxAttempts=3
|
|
330
|
+
}, 30_000);
|
|
331
|
+
|
|
332
|
+
it('calls onRetry callback', async () => {
|
|
333
|
+
const onRetry = vi.fn();
|
|
334
|
+
let calls = 0;
|
|
335
|
+
const fn = async () => {
|
|
336
|
+
calls++;
|
|
337
|
+
if (calls < 2) throw Object.assign(new Error('net'), { code: 'ECONNRESET' });
|
|
338
|
+
return 'ok';
|
|
339
|
+
};
|
|
340
|
+
await retryWithPreset(fn, 'fast', { onRetry });
|
|
341
|
+
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
342
|
+
expect(onRetry).toHaveBeenCalledWith(expect.any(SoleriError), 1, expect.any(Number));
|
|
343
|
+
}, 30_000);
|
|
344
|
+
|
|
345
|
+
it('respects abort signal', async () => {
|
|
346
|
+
const controller = new AbortController();
|
|
347
|
+
let calls = 0;
|
|
348
|
+
const fn = async () => {
|
|
349
|
+
calls++;
|
|
350
|
+
if (calls === 1) {
|
|
351
|
+
controller.abort();
|
|
352
|
+
throw Object.assign(new Error('net'), { code: 'ECONNREFUSED' });
|
|
353
|
+
}
|
|
354
|
+
return 'ok';
|
|
355
|
+
};
|
|
356
|
+
const result = await retryWithPreset(fn, 'fast', { signal: controller.signal });
|
|
357
|
+
expect(result.ok).toBe(false);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// ─── Result helpers ───────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
describe('Result helpers', () => {
|
|
364
|
+
it('ok() creates success result', () => {
|
|
365
|
+
const r = ok(42);
|
|
366
|
+
expect(r.ok).toBe(true);
|
|
367
|
+
if (r.ok) expect(r.value).toBe(42);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('err() creates failure result', () => {
|
|
371
|
+
const error = new SoleriError('fail', SoleriErrorCode.INTERNAL);
|
|
372
|
+
const r = err(error);
|
|
373
|
+
expect(r.ok).toBe(false);
|
|
374
|
+
if (!r.ok) expect(r.error).toBe(error);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('isOk() type guard works', () => {
|
|
378
|
+
const r = ok('hello');
|
|
379
|
+
expect(isOk(r)).toBe(true);
|
|
380
|
+
expect(isErr(r)).toBe(false);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('isErr() type guard works', () => {
|
|
384
|
+
const r = err(new SoleriError('x', SoleriErrorCode.AUTH));
|
|
385
|
+
expect(isErr(r)).toBe(true);
|
|
386
|
+
expect(isOk(r)).toBe(false);
|
|
387
|
+
});
|
|
388
|
+
});
|