@vorionsys/atsf-core 0.2.3 → 0.2.4
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/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/server.d.ts +2 -2
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +147 -184
- package/dist/api/server.js.map +1 -1
- package/dist/arbitration/index.d.ts +4 -4
- package/dist/arbitration/index.d.ts.map +1 -1
- package/dist/arbitration/index.js +41 -46
- package/dist/arbitration/index.js.map +1 -1
- package/dist/arbitration/types.d.ts +10 -10
- package/dist/arbitration/types.d.ts.map +1 -1
- package/dist/basis/evaluator.d.ts +1 -1
- package/dist/basis/evaluator.d.ts.map +1 -1
- package/dist/basis/evaluator.js +54 -56
- package/dist/basis/evaluator.js.map +1 -1
- package/dist/basis/index.d.ts +3 -3
- package/dist/basis/index.js +3 -3
- package/dist/basis/parser.d.ts +2 -2
- package/dist/basis/parser.d.ts.map +1 -1
- package/dist/basis/parser.js +25 -32
- package/dist/basis/parser.js.map +1 -1
- package/dist/basis/types.d.ts +2 -2
- package/dist/chain/index.d.ts.map +1 -1
- package/dist/chain/index.js +16 -16
- package/dist/chain/index.js.map +1 -1
- package/dist/cognigate/index.d.ts +1 -1
- package/dist/cognigate/index.d.ts.map +1 -1
- package/dist/cognigate/index.js +33 -44
- package/dist/cognigate/index.js.map +1 -1
- package/dist/common/adapters.d.ts +4 -4
- package/dist/common/adapters.d.ts.map +1 -1
- package/dist/common/adapters.js +52 -62
- package/dist/common/adapters.js.map +1 -1
- package/dist/common/config.d.ts +69 -68
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +50 -50
- package/dist/common/config.js.map +1 -1
- package/dist/common/index.d.ts +4 -4
- package/dist/common/index.js +4 -4
- package/dist/common/logger.d.ts +1 -1
- package/dist/common/logger.js +8 -8
- package/dist/common/types.d.ts +5 -5
- package/dist/common/types.js +5 -5
- package/dist/containment/index.d.ts +3 -3
- package/dist/containment/index.d.ts.map +1 -1
- package/dist/containment/index.js +105 -119
- package/dist/containment/index.js.map +1 -1
- package/dist/containment/types.d.ts +11 -11
- package/dist/containment/types.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +9 -9
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +54 -59
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/types.d.ts +12 -12
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/crewai/callback.d.ts +2 -2
- package/dist/crewai/callback.d.ts.map +1 -1
- package/dist/crewai/callback.js +27 -27
- package/dist/crewai/callback.js.map +1 -1
- package/dist/crewai/executor.d.ts +95 -4
- package/dist/crewai/executor.d.ts.map +1 -1
- package/dist/crewai/executor.js +457 -16
- package/dist/crewai/executor.js.map +1 -1
- package/dist/crewai/index.d.ts +4 -4
- package/dist/crewai/index.js +4 -4
- package/dist/crewai/tools.d.ts +1 -1
- package/dist/crewai/tools.d.ts.map +1 -1
- package/dist/crewai/tools.js +38 -39
- package/dist/crewai/tools.js.map +1 -1
- package/dist/crewai/types.d.ts +66 -3
- package/dist/crewai/types.d.ts.map +1 -1
- package/dist/enforce/index.d.ts +229 -7
- package/dist/enforce/index.d.ts.map +1 -1
- package/dist/enforce/index.js +52 -80
- package/dist/enforce/index.js.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.d.ts +8 -8
- package/dist/enforce/trust-aware-enforcement-service.d.ts.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.js +107 -125
- package/dist/enforce/trust-aware-enforcement-service.js.map +1 -1
- package/dist/governance/fluid-workflow.d.ts +8 -8
- package/dist/governance/fluid-workflow.d.ts.map +1 -1
- package/dist/governance/fluid-workflow.js +86 -114
- package/dist/governance/fluid-workflow.js.map +1 -1
- package/dist/governance/index.d.ts +7 -7
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/index.js +74 -81
- package/dist/governance/index.js.map +1 -1
- package/dist/governance/proof-bridge.d.ts +6 -6
- package/dist/governance/proof-bridge.d.ts.map +1 -1
- package/dist/governance/proof-bridge.js +5 -5
- package/dist/governance/proof-bridge.js.map +1 -1
- package/dist/governance/types.d.ts +9 -16
- package/dist/governance/types.d.ts.map +1 -1
- package/dist/governance/types.js.map +1 -1
- package/dist/index.d.ts +27 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -31
- package/dist/index.js.map +1 -1
- package/dist/intent/index.d.ts +55 -5
- package/dist/intent/index.d.ts.map +1 -1
- package/dist/intent/index.js +21 -24
- package/dist/intent/index.js.map +1 -1
- package/dist/intent/persistent-intent-service.d.ts +2 -2
- package/dist/intent/persistent-intent-service.d.ts.map +1 -1
- package/dist/intent/persistent-intent-service.js +31 -43
- package/dist/intent/persistent-intent-service.js.map +1 -1
- package/dist/intent/supabase-intent-repository.d.ts +124 -0
- package/dist/intent/supabase-intent-repository.d.ts.map +1 -0
- package/dist/intent/supabase-intent-repository.js +404 -0
- package/dist/intent/supabase-intent-repository.js.map +1 -0
- package/dist/langchain/callback.d.ts +2 -2
- package/dist/langchain/callback.d.ts.map +1 -1
- package/dist/langchain/callback.js +30 -30
- package/dist/langchain/callback.js.map +1 -1
- package/dist/langchain/executor.d.ts +4 -4
- package/dist/langchain/executor.d.ts.map +1 -1
- package/dist/langchain/executor.js +80 -82
- package/dist/langchain/executor.js.map +1 -1
- package/dist/langchain/index.d.ts +5 -5
- package/dist/langchain/index.js +5 -5
- package/dist/langchain/tools.d.ts +1 -1
- package/dist/langchain/tools.d.ts.map +1 -1
- package/dist/langchain/tools.js +34 -36
- package/dist/langchain/tools.js.map +1 -1
- package/dist/langchain/types.d.ts +3 -3
- package/dist/langchain/types.d.ts.map +1 -1
- package/dist/layers/implementations/L0-request-format.d.ts +2 -2
- package/dist/layers/implementations/L0-request-format.d.ts.map +1 -1
- package/dist/layers/implementations/L0-request-format.js +52 -54
- package/dist/layers/implementations/L0-request-format.js.map +1 -1
- package/dist/layers/implementations/L1-input-size.d.ts +2 -2
- package/dist/layers/implementations/L1-input-size.d.ts.map +1 -1
- package/dist/layers/implementations/L1-input-size.js +39 -49
- package/dist/layers/implementations/L1-input-size.js.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.d.ts +2 -2
- package/dist/layers/implementations/L2-charset-sanitizer.d.ts.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.js +71 -81
- package/dist/layers/implementations/L2-charset-sanitizer.js.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.d.ts +3 -3
- package/dist/layers/implementations/L3-schema-conformance.d.ts.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.js +73 -82
- package/dist/layers/implementations/L3-schema-conformance.js.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.d.ts +4 -4
- package/dist/layers/implementations/L4-injection-detector.d.ts.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.js +81 -85
- package/dist/layers/implementations/L4-injection-detector.js.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.d.ts +2 -2
- package/dist/layers/implementations/L5-rate-limiter.d.ts.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.js +20 -20
- package/dist/layers/implementations/L5-rate-limiter.js.map +1 -1
- package/dist/layers/implementations/index.d.ts +6 -6
- package/dist/layers/implementations/index.d.ts.map +1 -1
- package/dist/layers/implementations/index.js +6 -6
- package/dist/layers/implementations/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -3
- package/dist/layers/index.d.ts.map +1 -1
- package/dist/layers/index.js +71 -99
- package/dist/layers/index.js.map +1 -1
- package/dist/layers/types.d.ts +16 -16
- package/dist/layers/types.d.ts.map +1 -1
- package/dist/persistence/file.d.ts +3 -3
- package/dist/persistence/file.d.ts.map +1 -1
- package/dist/persistence/file.js +28 -32
- package/dist/persistence/file.js.map +1 -1
- package/dist/persistence/index.d.ts +7 -7
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +18 -18
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/memory.d.ts +3 -3
- package/dist/persistence/memory.d.ts.map +1 -1
- package/dist/persistence/memory.js +8 -10
- package/dist/persistence/memory.js.map +1 -1
- package/dist/persistence/sqlite.d.ts +3 -3
- package/dist/persistence/sqlite.d.ts.map +1 -1
- package/dist/persistence/sqlite.js +40 -39
- package/dist/persistence/sqlite.js.map +1 -1
- package/dist/persistence/supabase.d.ts +3 -3
- package/dist/persistence/supabase.d.ts.map +1 -1
- package/dist/persistence/supabase.js +45 -43
- package/dist/persistence/supabase.js.map +1 -1
- package/dist/persistence/types.d.ts +5 -5
- package/dist/phase6/ceiling.d.ts +5 -5
- package/dist/phase6/ceiling.d.ts.map +1 -1
- package/dist/phase6/ceiling.js +36 -69
- package/dist/phase6/ceiling.js.map +1 -1
- package/dist/phase6/context.d.ts +3 -3
- package/dist/phase6/context.d.ts.map +1 -1
- package/dist/phase6/context.js +47 -93
- package/dist/phase6/context.js.map +1 -1
- package/dist/phase6/index.d.ts +12 -12
- package/dist/phase6/index.d.ts.map +1 -1
- package/dist/phase6/index.js +15 -15
- package/dist/phase6/index.js.map +1 -1
- package/dist/phase6/presets.d.ts +2 -2
- package/dist/phase6/presets.d.ts.map +1 -1
- package/dist/phase6/presets.js +33 -39
- package/dist/phase6/presets.js.map +1 -1
- package/dist/phase6/provenance.d.ts +4 -4
- package/dist/phase6/provenance.d.ts.map +1 -1
- package/dist/phase6/provenance.js +35 -42
- package/dist/phase6/provenance.js.map +1 -1
- package/dist/phase6/role-gates/index.d.ts +2 -2
- package/dist/phase6/role-gates/index.js +2 -2
- package/dist/phase6/role-gates/kernel.d.ts.map +1 -1
- package/dist/phase6/role-gates/kernel.js +16 -16
- package/dist/phase6/role-gates/kernel.js.map +1 -1
- package/dist/phase6/role-gates/policy.d.ts +2 -2
- package/dist/phase6/role-gates/policy.js +6 -6
- package/dist/phase6/role-gates.d.ts +4 -4
- package/dist/phase6/role-gates.d.ts.map +1 -1
- package/dist/phase6/role-gates.js +58 -80
- package/dist/phase6/role-gates.js.map +1 -1
- package/dist/phase6/types.d.ts +20 -19
- package/dist/phase6/types.d.ts.map +1 -1
- package/dist/phase6/types.js +82 -177
- package/dist/phase6/types.js.map +1 -1
- package/dist/phase6/weight-presets/canonical.d.ts.map +1 -1
- package/dist/phase6/weight-presets/canonical.js +10 -10
- package/dist/phase6/weight-presets/canonical.js.map +1 -1
- package/dist/phase6/weight-presets/deltas.d.ts +2 -2
- package/dist/phase6/weight-presets/deltas.d.ts.map +1 -1
- package/dist/phase6/weight-presets/deltas.js +27 -27
- package/dist/phase6/weight-presets/deltas.js.map +1 -1
- package/dist/phase6/weight-presets/index.d.ts +3 -3
- package/dist/phase6/weight-presets/index.js +3 -3
- package/dist/phase6/weight-presets/merger.d.ts +2 -2
- package/dist/phase6/weight-presets/merger.d.ts.map +1 -1
- package/dist/phase6/weight-presets/merger.js +43 -39
- package/dist/phase6/weight-presets/merger.js.map +1 -1
- package/dist/proof/index.d.ts +3 -3
- package/dist/proof/index.d.ts.map +1 -1
- package/dist/proof/index.js +38 -44
- package/dist/proof/index.js.map +1 -1
- package/dist/proof/merkle.d.ts +24 -3
- package/dist/proof/merkle.d.ts.map +1 -1
- package/dist/proof/merkle.js +116 -32
- package/dist/proof/merkle.js.map +1 -1
- package/dist/proof/zk-proofs.d.ts +6 -6
- package/dist/proof/zk-proofs.d.ts.map +1 -1
- package/dist/proof/zk-proofs.js +43 -42
- package/dist/proof/zk-proofs.js.map +1 -1
- package/dist/provenance/index.d.ts +3 -3
- package/dist/provenance/index.d.ts.map +1 -1
- package/dist/provenance/index.js +17 -19
- package/dist/provenance/index.js.map +1 -1
- package/dist/provenance/types.d.ts +4 -4
- package/dist/provenance/types.d.ts.map +1 -1
- package/dist/sandbox-training/challenges.d.ts +1 -1
- package/dist/sandbox-training/challenges.d.ts.map +1 -1
- package/dist/sandbox-training/challenges.js +228 -228
- package/dist/sandbox-training/challenges.js.map +1 -1
- package/dist/sandbox-training/graduation.d.ts +1 -1
- package/dist/sandbox-training/graduation.d.ts.map +1 -1
- package/dist/sandbox-training/graduation.js +15 -14
- package/dist/sandbox-training/graduation.js.map +1 -1
- package/dist/sandbox-training/index.d.ts +9 -9
- package/dist/sandbox-training/index.d.ts.map +1 -1
- package/dist/sandbox-training/index.js +6 -6
- package/dist/sandbox-training/index.js.map +1 -1
- package/dist/sandbox-training/promotion-service.d.ts +4 -4
- package/dist/sandbox-training/promotion-service.d.ts.map +1 -1
- package/dist/sandbox-training/promotion-service.js +5 -5
- package/dist/sandbox-training/promotion-service.js.map +1 -1
- package/dist/sandbox-training/runner.d.ts +1 -1
- package/dist/sandbox-training/runner.d.ts.map +1 -1
- package/dist/sandbox-training/runner.js +73 -74
- package/dist/sandbox-training/runner.js.map +1 -1
- package/dist/sandbox-training/scorer.d.ts +4 -4
- package/dist/sandbox-training/scorer.js +5 -5
- package/dist/sandbox-training/types.d.ts +4 -4
- package/dist/sandbox-training/types.d.ts.map +1 -1
- package/dist/sandbox-training/types.js +7 -11
- package/dist/sandbox-training/types.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.js +4 -3
- package/dist/trust-engine/ceiling-enforcement/audit.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/index.d.ts +2 -2
- package/dist/trust-engine/ceiling-enforcement/index.js +2 -2
- package/dist/trust-engine/ceiling-enforcement/kernel.d.ts +12 -10
- package/dist/trust-engine/ceiling-enforcement/kernel.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/kernel.js +26 -20
- package/dist/trust-engine/ceiling-enforcement/kernel.js.map +1 -1
- package/dist/trust-engine/context-policy/enforcement.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/factory.d.ts +1 -1
- package/dist/trust-engine/context-policy/factory.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/factory.js +1 -1
- package/dist/trust-engine/context-policy/factory.js.map +1 -1
- package/dist/trust-engine/context-policy/index.d.ts +2 -2
- package/dist/trust-engine/context-policy/index.js +2 -2
- package/dist/trust-engine/creation-modifiers/index.d.ts +1 -1
- package/dist/trust-engine/creation-modifiers/index.js +1 -1
- package/dist/trust-engine/creation-modifiers/types.d.ts.map +1 -1
- package/dist/trust-engine/creation-modifiers/types.js +3 -2
- package/dist/trust-engine/creation-modifiers/types.js.map +1 -1
- package/dist/trust-engine/decay-profiles.d.ts +37 -136
- package/dist/trust-engine/decay-profiles.d.ts.map +1 -1
- package/dist/trust-engine/decay-profiles.js +68 -178
- package/dist/trust-engine/decay-profiles.js.map +1 -1
- package/dist/trust-engine/index.d.ts +135 -168
- package/dist/trust-engine/index.d.ts.map +1 -1
- package/dist/trust-engine/index.js +239 -525
- package/dist/trust-engine/index.js.map +1 -1
- package/dist/trust-engine/phase6-types.d.ts +18 -11
- package/dist/trust-engine/phase6-types.d.ts.map +1 -1
- package/dist/trust-engine/phase6-types.js +33 -29
- package/dist/trust-engine/phase6-types.js.map +1 -1
- package/package.json +1 -1
- package/dist/enforce/types.d.ts +0 -234
- package/dist/enforce/types.d.ts.map +0 -1
- package/dist/enforce/types.js +0 -10
- package/dist/enforce/types.js.map +0 -1
- package/dist/intent/types.d.ts +0 -69
- package/dist/intent/types.d.ts.map +0 -1
- package/dist/intent/types.js +0 -10
- package/dist/intent/types.js.map +0 -1
- package/dist/intent-gateway/index.d.ts +0 -522
- package/dist/intent-gateway/index.d.ts.map +0 -1
- package/dist/intent-gateway/index.js +0 -1499
- package/dist/intent-gateway/index.js.map +0 -1
- package/dist/trust-engine/types.d.ts +0 -77
- package/dist/trust-engine/types.d.ts.map +0 -1
- package/dist/trust-engine/types.js +0 -20
- package/dist/trust-engine/types.js.map +0 -1
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Trust Engine - Behavioral Trust Scoring
|
|
3
3
|
*
|
|
4
4
|
* Calculates and maintains trust scores for entities based on behavioral signals.
|
|
5
|
-
* Features
|
|
5
|
+
* Features 8-tier trust system (T0-T7) with event emission for observability.
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
-
import { EventEmitter } from
|
|
10
|
-
import { createLogger } from
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
const logger = createLogger({ component:
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { createLogger } from '../common/logger.js';
|
|
11
|
+
import { calculateDecayMultiplier } from './decay-profiles.js';
|
|
12
|
+
import { FACTOR_CODE_LIST, DEFAULT_FACTOR_WEIGHTS, SIGNAL_PREFIX_TO_FACTORS as BASIS_SIGNAL_PREFIX_MAP, initialFactorScores, } from '@vorionsys/basis';
|
|
13
|
+
const logger = createLogger({ component: 'trust-engine' });
|
|
14
14
|
/**
|
|
15
15
|
* Trust level thresholds (8 tiers T0-T7) - per BASIS specification
|
|
16
16
|
*/
|
|
@@ -28,16 +28,22 @@ export const TRUST_THRESHOLDS = {
|
|
|
28
28
|
* Trust level names (8 tiers T0-T7) - per BASIS specification
|
|
29
29
|
*/
|
|
30
30
|
export const TRUST_LEVEL_NAMES = {
|
|
31
|
-
0:
|
|
32
|
-
1:
|
|
33
|
-
2:
|
|
34
|
-
3:
|
|
35
|
-
4:
|
|
36
|
-
5:
|
|
37
|
-
6:
|
|
38
|
-
7:
|
|
31
|
+
0: 'Sandbox',
|
|
32
|
+
1: 'Observed',
|
|
33
|
+
2: 'Provisional',
|
|
34
|
+
3: 'Monitored',
|
|
35
|
+
4: 'Standard',
|
|
36
|
+
5: 'Trusted',
|
|
37
|
+
6: 'Certified',
|
|
38
|
+
7: 'Autonomous',
|
|
39
39
|
};
|
|
40
|
+
// Re-export canonical factor constants from @vorionsys/basis
|
|
41
|
+
export const FACTOR_CODES = FACTOR_CODE_LIST;
|
|
42
|
+
export const FACTOR_WEIGHTS = DEFAULT_FACTOR_WEIGHTS;
|
|
43
|
+
export const SIGNAL_PREFIX_TO_FACTORS = BASIS_SIGNAL_PREFIX_MAP;
|
|
44
|
+
export { initialFactorScores };
|
|
40
45
|
/**
|
|
46
|
+
* @deprecated Use FACTOR_WEIGHTS for 16-factor scoring. Kept for backwards compatibility.
|
|
41
47
|
* Signal weights for score calculation
|
|
42
48
|
*/
|
|
43
49
|
export const SIGNAL_WEIGHTS = {
|
|
@@ -46,36 +52,12 @@ export const SIGNAL_WEIGHTS = {
|
|
|
46
52
|
identity: 0.2,
|
|
47
53
|
context: 0.15,
|
|
48
54
|
};
|
|
49
|
-
const DEFAULT_READINESS_CHECKPOINT_DAYS = [
|
|
50
|
-
7, 14, 28, 42, 56, 84, 112, 140, 182,
|
|
51
|
-
];
|
|
52
|
-
const DEFAULT_READINESS_REDUCTIONS = [
|
|
53
|
-
0.06, 0.06, 0.06, 0.06, 0.06, 0.05, 0.05, 0.05, 0.05,
|
|
54
|
-
];
|
|
55
|
-
const DEFAULT_READINESS_EXCEPTION_MAX_DURATION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
56
|
-
function toMs(days) {
|
|
57
|
-
return days * 24 * 60 * 60 * 1000;
|
|
58
|
-
}
|
|
59
|
-
function clampReductionScale(value) {
|
|
60
|
-
if (value === undefined || Number.isNaN(value))
|
|
61
|
-
return 1;
|
|
62
|
-
if (value < 0)
|
|
63
|
-
return 0;
|
|
64
|
-
if (value > 1)
|
|
65
|
-
return 1;
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
55
|
/**
|
|
69
56
|
* Trust Engine service with event emission and subscription limits
|
|
70
57
|
*/
|
|
71
58
|
export class TrustEngine extends EventEmitter {
|
|
72
59
|
records = new Map();
|
|
73
|
-
|
|
74
|
-
_decayIntervalMs;
|
|
75
|
-
_failureThreshold;
|
|
76
|
-
_acceleratedDecayMultiplier;
|
|
77
|
-
_failureWindowMs;
|
|
78
|
-
_minFailuresForAcceleration;
|
|
60
|
+
_decayCheckIntervalMs;
|
|
79
61
|
_persistence;
|
|
80
62
|
_autoPersist;
|
|
81
63
|
// Recovery configuration
|
|
@@ -91,26 +73,15 @@ export class TrustEngine extends EventEmitter {
|
|
|
91
73
|
_listenerWarningThreshold;
|
|
92
74
|
_listenerCounts = new Map();
|
|
93
75
|
_totalListeners = 0;
|
|
94
|
-
_readinessMode;
|
|
95
|
-
_readinessCheckpointDays;
|
|
96
|
-
_readinessCheckpointReductions;
|
|
97
|
-
_allowedReadinessExceptionReasons;
|
|
98
|
-
_readinessExceptionMaxDurationMs;
|
|
99
76
|
constructor(config = {}) {
|
|
100
77
|
super();
|
|
101
|
-
this.
|
|
102
|
-
this._decayIntervalMs = config.decayIntervalMs ?? 60000;
|
|
103
|
-
this._failureThreshold = config.failureThreshold ?? 0.3;
|
|
104
|
-
this._acceleratedDecayMultiplier = config.acceleratedDecayMultiplier ?? 1.0;
|
|
105
|
-
this._failureWindowMs = config.failureWindowMs ?? 3600000; // 1 hour
|
|
106
|
-
this._minFailuresForAcceleration = config.minFailuresForAcceleration ?? 2;
|
|
78
|
+
this._decayCheckIntervalMs = config.decayCheckIntervalMs ?? 60000;
|
|
107
79
|
this._persistence = config.persistence;
|
|
108
|
-
this._autoPersist = config.autoPersist ?? config.persistence !== undefined;
|
|
80
|
+
this._autoPersist = config.autoPersist ?? (config.persistence !== undefined);
|
|
109
81
|
// Recovery configuration
|
|
110
82
|
this._successThreshold = config.successThreshold ?? 0.7;
|
|
111
83
|
this._recoveryRate = config.recoveryRate ?? 0.02;
|
|
112
|
-
this._acceleratedRecoveryMultiplier =
|
|
113
|
-
config.acceleratedRecoveryMultiplier ?? 1.5;
|
|
84
|
+
this._acceleratedRecoveryMultiplier = config.acceleratedRecoveryMultiplier ?? 1.5;
|
|
114
85
|
this._minSuccessesForAcceleration = config.minSuccessesForAcceleration ?? 3;
|
|
115
86
|
this._successWindowMs = config.successWindowMs ?? 3600000; // 1 hour
|
|
116
87
|
this._maxRecoveryPerSignal = config.maxRecoveryPerSignal ?? 50;
|
|
@@ -118,219 +89,9 @@ export class TrustEngine extends EventEmitter {
|
|
|
118
89
|
this._maxListenersPerEvent = config.maxListenersPerEvent ?? 100;
|
|
119
90
|
this._maxTotalListeners = config.maxTotalListeners ?? 1000;
|
|
120
91
|
this._listenerWarningThreshold = config.listenerWarningThreshold ?? 0.8;
|
|
121
|
-
this._readinessMode =
|
|
122
|
-
config.readinessMode ??
|
|
123
|
-
config.freshnessMode ??
|
|
124
|
-
(config.decayIntervalMs !== undefined || config.decayRate !== undefined
|
|
125
|
-
? "legacy_interval"
|
|
126
|
-
: "checkpoint_schedule");
|
|
127
|
-
this._readinessCheckpointDays = config.readinessCheckpointDays ??
|
|
128
|
-
config.freshnessCheckpointDays ?? [...DEFAULT_READINESS_CHECKPOINT_DAYS];
|
|
129
|
-
this._readinessCheckpointReductions =
|
|
130
|
-
config.readinessCheckpointReductions ??
|
|
131
|
-
config.freshnessCheckpointReductions ?? [
|
|
132
|
-
...DEFAULT_READINESS_REDUCTIONS,
|
|
133
|
-
];
|
|
134
|
-
if (this._readinessCheckpointDays.length !==
|
|
135
|
-
this._readinessCheckpointReductions.length) {
|
|
136
|
-
throw new Error("freshnessCheckpointDays and freshnessCheckpointReductions must have equal length");
|
|
137
|
-
}
|
|
138
|
-
this._allowedReadinessExceptionReasons = new Set(config.readinessExceptionAllowedReasons ??
|
|
139
|
-
config.freshnessExceptionAllowedReasons ?? [
|
|
140
|
-
...READINESS_EXCEPTION_REASON_CODES,
|
|
141
|
-
]);
|
|
142
|
-
this._readinessExceptionMaxDurationMs =
|
|
143
|
-
config.readinessExceptionMaxDurationMs ??
|
|
144
|
-
config.freshnessExceptionMaxDurationMs ??
|
|
145
|
-
DEFAULT_READINESS_EXCEPTION_MAX_DURATION_MS;
|
|
146
92
|
// Set default max listeners on EventEmitter
|
|
147
93
|
this.setMaxListeners(this._maxListenersPerEvent);
|
|
148
94
|
}
|
|
149
|
-
validateReadinessExceptionOptions(options) {
|
|
150
|
-
if (!this._allowedReadinessExceptionReasons.has(options.reason)) {
|
|
151
|
-
throw new Error(`Unsupported readiness exception reason: ${options.reason}. ` +
|
|
152
|
-
`Allowed reasons: ${Array.from(this._allowedReadinessExceptionReasons).join(", ")}`);
|
|
153
|
-
}
|
|
154
|
-
const now = Date.now();
|
|
155
|
-
const expiresAtMs = new Date(options.expiresAt).getTime();
|
|
156
|
-
if (Number.isNaN(expiresAtMs)) {
|
|
157
|
-
throw new Error("Invalid expiresAt timestamp for readiness exception");
|
|
158
|
-
}
|
|
159
|
-
if (expiresAtMs <= now) {
|
|
160
|
-
throw new Error("Readiness exception expiresAt must be in the future");
|
|
161
|
-
}
|
|
162
|
-
if (expiresAtMs - now > this._readinessExceptionMaxDurationMs) {
|
|
163
|
-
throw new Error(`Readiness exception duration exceeds configured maximum (${this._readinessExceptionMaxDurationMs} ms)`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
getCheckpointIntervalMs(index) {
|
|
167
|
-
if (index <= 0) {
|
|
168
|
-
return toMs(this._readinessCheckpointDays[0] ?? 7);
|
|
169
|
-
}
|
|
170
|
-
const current = this._readinessCheckpointDays[index] ??
|
|
171
|
-
this._readinessCheckpointDays[this._readinessCheckpointDays.length - 1] ??
|
|
172
|
-
7;
|
|
173
|
-
const previous = this._readinessCheckpointDays[index - 1] ?? 0;
|
|
174
|
-
return toMs(Math.max(1, current - previous));
|
|
175
|
-
}
|
|
176
|
-
ensureReadinessState(record) {
|
|
177
|
-
record.readinessCheckpointIndex ??= record.freshnessCheckpointIndex ?? 0;
|
|
178
|
-
record.deferredReadinessMultiplier ??=
|
|
179
|
-
record.deferredFreshnessMultiplier ?? 1;
|
|
180
|
-
record.readinessBaselineScore ??=
|
|
181
|
-
record.freshnessBaselineScore ?? record.score;
|
|
182
|
-
record.freshnessCheckpointIndex = record.readinessCheckpointIndex;
|
|
183
|
-
record.deferredFreshnessMultiplier = record.deferredReadinessMultiplier;
|
|
184
|
-
record.freshnessBaselineScore = record.readinessBaselineScore;
|
|
185
|
-
record.freshnessException =
|
|
186
|
-
record.readinessException ?? record.freshnessException;
|
|
187
|
-
}
|
|
188
|
-
isUsingDefaultReadinessSchedule() {
|
|
189
|
-
if (this._readinessCheckpointDays.length !==
|
|
190
|
-
DEFAULT_READINESS_CHECKPOINT_DAYS.length) {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
if (this._readinessCheckpointReductions.length !==
|
|
194
|
-
DEFAULT_READINESS_REDUCTIONS.length) {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
return (this._readinessCheckpointDays.every((v, i) => v === DEFAULT_READINESS_CHECKPOINT_DAYS[i]) &&
|
|
198
|
-
this._readinessCheckpointReductions.every((v, i) => v === DEFAULT_READINESS_REDUCTIONS[i]));
|
|
199
|
-
}
|
|
200
|
-
isReadinessExceptionActive(record, now) {
|
|
201
|
-
const exception = record.readinessException ?? record.freshnessException;
|
|
202
|
-
if (!exception)
|
|
203
|
-
return false;
|
|
204
|
-
const issuedAt = new Date(exception.issuedAt).getTime();
|
|
205
|
-
const expiresAt = new Date(exception.expiresAt).getTime();
|
|
206
|
-
return now >= issuedAt && now < expiresAt;
|
|
207
|
-
}
|
|
208
|
-
async applyDeferredReadinessCatchupIfExpired(record) {
|
|
209
|
-
const exception = record.readinessException ?? record.freshnessException;
|
|
210
|
-
if (!exception)
|
|
211
|
-
return;
|
|
212
|
-
const now = Date.now();
|
|
213
|
-
const expiresAt = new Date(exception.expiresAt).getTime();
|
|
214
|
-
if (now < expiresAt)
|
|
215
|
-
return;
|
|
216
|
-
this.ensureReadinessState(record);
|
|
217
|
-
const deferredMultiplier = record.deferredReadinessMultiplier ?? 1;
|
|
218
|
-
if (deferredMultiplier < 1) {
|
|
219
|
-
const previousScore = record.score;
|
|
220
|
-
const adjustedScore = Math.max(0, Math.round(record.score * deferredMultiplier));
|
|
221
|
-
record.score = adjustedScore;
|
|
222
|
-
record.level = this.scoreToLevel(adjustedScore);
|
|
223
|
-
record.deferredReadinessMultiplier = 1;
|
|
224
|
-
record.deferredFreshnessMultiplier = 1;
|
|
225
|
-
this.emitReadinessAdjustmentEvents(record.entityId, {
|
|
226
|
-
previousScore,
|
|
227
|
-
newScore: record.score,
|
|
228
|
-
stalenessMs: 0,
|
|
229
|
-
accelerated: false,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
delete record.readinessException;
|
|
233
|
-
delete record.freshnessException;
|
|
234
|
-
}
|
|
235
|
-
async applyScheduledReadinessAdjustment(record) {
|
|
236
|
-
this.ensureReadinessState(record);
|
|
237
|
-
await this.applyDeferredReadinessCatchupIfExpired(record);
|
|
238
|
-
while ((record.readinessCheckpointIndex ?? 0) <
|
|
239
|
-
this._readinessCheckpointDays.length) {
|
|
240
|
-
const checkpointIndex = record.readinessCheckpointIndex ?? 0;
|
|
241
|
-
const intervalMs = this.getCheckpointIntervalMs(checkpointIndex);
|
|
242
|
-
const stalenessMs = Date.now() - new Date(record.lastCalculatedAt).getTime();
|
|
243
|
-
if (stalenessMs < intervalMs) {
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
const previousScore = record.score;
|
|
247
|
-
const previousLevel = record.level;
|
|
248
|
-
const fullReduction = this._readinessCheckpointReductions[checkpointIndex] ?? 0;
|
|
249
|
-
const activeException = this.isReadinessExceptionActive(record, Date.now());
|
|
250
|
-
const scale = activeException
|
|
251
|
-
? clampReductionScale((record.readinessException ?? record.freshnessException)
|
|
252
|
-
?.reductionScale)
|
|
253
|
-
: 1;
|
|
254
|
-
const appliedReduction = fullReduction * scale;
|
|
255
|
-
const appliedMultiplier = 1 - appliedReduction;
|
|
256
|
-
const fullMultiplier = 1 - fullReduction;
|
|
257
|
-
record.score = Math.max(0, Math.round(record.score * appliedMultiplier));
|
|
258
|
-
const isFinalCheckpoint = checkpointIndex === this._readinessCheckpointDays.length - 1;
|
|
259
|
-
if (isFinalCheckpoint &&
|
|
260
|
-
!activeException &&
|
|
261
|
-
this.isUsingDefaultReadinessSchedule()) {
|
|
262
|
-
const baseline = record.readinessBaselineScore ??
|
|
263
|
-
record.freshnessBaselineScore ??
|
|
264
|
-
record.score;
|
|
265
|
-
record.score = Math.max(0, Math.round(baseline * 0.5));
|
|
266
|
-
}
|
|
267
|
-
record.level = this.scoreToLevel(record.score);
|
|
268
|
-
record.readinessCheckpointIndex = checkpointIndex + 1;
|
|
269
|
-
record.freshnessCheckpointIndex = record.readinessCheckpointIndex;
|
|
270
|
-
const lastCalculatedMs = new Date(record.lastCalculatedAt).getTime();
|
|
271
|
-
record.lastCalculatedAt = new Date(lastCalculatedMs + intervalMs).toISOString();
|
|
272
|
-
if (activeException && appliedMultiplier > 0 && fullMultiplier >= 0) {
|
|
273
|
-
const debtFactor = fullMultiplier / appliedMultiplier;
|
|
274
|
-
record.deferredReadinessMultiplier =
|
|
275
|
-
(record.deferredReadinessMultiplier ?? 1) * debtFactor;
|
|
276
|
-
record.deferredFreshnessMultiplier = record.deferredReadinessMultiplier;
|
|
277
|
-
}
|
|
278
|
-
if (previousScore !== record.score) {
|
|
279
|
-
this.emitReadinessAdjustmentEvents(record.entityId, {
|
|
280
|
-
previousScore,
|
|
281
|
-
newScore: record.score,
|
|
282
|
-
stalenessMs,
|
|
283
|
-
accelerated: false,
|
|
284
|
-
});
|
|
285
|
-
if (previousLevel !== record.level) {
|
|
286
|
-
this.emitTrustEvent({
|
|
287
|
-
type: "trust:tier_changed",
|
|
288
|
-
entityId: record.entityId,
|
|
289
|
-
timestamp: new Date().toISOString(),
|
|
290
|
-
previousLevel,
|
|
291
|
-
newLevel: record.level,
|
|
292
|
-
previousLevelName: TRUST_LEVEL_NAMES[previousLevel],
|
|
293
|
-
newLevelName: TRUST_LEVEL_NAMES[record.level],
|
|
294
|
-
direction: record.level < previousLevel ? "demoted" : "promoted",
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
await this.autoPersistRecord(record);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
emitReadinessAdjustmentEvents(entityId, details) {
|
|
302
|
-
const adjustmentAmount = details.previousScore - details.newScore;
|
|
303
|
-
this.emitTrustEvent({
|
|
304
|
-
type: "trust:readiness_adjusted",
|
|
305
|
-
entityId,
|
|
306
|
-
timestamp: new Date().toISOString(),
|
|
307
|
-
previousScore: details.previousScore,
|
|
308
|
-
newScore: details.newScore,
|
|
309
|
-
adjustmentAmount,
|
|
310
|
-
stalenessMs: details.stalenessMs,
|
|
311
|
-
accelerated: details.accelerated,
|
|
312
|
-
});
|
|
313
|
-
this.emitTrustEvent({
|
|
314
|
-
type: "trust:freshness_adjusted",
|
|
315
|
-
entityId,
|
|
316
|
-
timestamp: new Date().toISOString(),
|
|
317
|
-
previousScore: details.previousScore,
|
|
318
|
-
newScore: details.newScore,
|
|
319
|
-
adjustmentAmount,
|
|
320
|
-
stalenessMs: details.stalenessMs,
|
|
321
|
-
accelerated: details.accelerated,
|
|
322
|
-
});
|
|
323
|
-
this.emitTrustEvent({
|
|
324
|
-
type: "trust:decay_applied",
|
|
325
|
-
entityId,
|
|
326
|
-
timestamp: new Date().toISOString(),
|
|
327
|
-
previousScore: details.previousScore,
|
|
328
|
-
newScore: details.newScore,
|
|
329
|
-
decayAmount: adjustmentAmount,
|
|
330
|
-
stalenessMs: details.stalenessMs,
|
|
331
|
-
accelerated: details.accelerated,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
95
|
/**
|
|
335
96
|
* Add event listener with subscription limits
|
|
336
97
|
* @throws Error if listener limits are exceeded
|
|
@@ -401,14 +162,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
401
162
|
const eventThreshold = this._maxListenersPerEvent * this._listenerWarningThreshold;
|
|
402
163
|
const totalThreshold = this._maxTotalListeners * this._listenerWarningThreshold;
|
|
403
164
|
if (currentEventCount >= eventThreshold) {
|
|
404
|
-
logger.warn({
|
|
405
|
-
eventName,
|
|
406
|
-
current: currentEventCount,
|
|
407
|
-
max: this._maxListenersPerEvent,
|
|
408
|
-
}, `Approaching listener limit for event "${eventName}"`);
|
|
165
|
+
logger.warn({ eventName, current: currentEventCount, max: this._maxListenersPerEvent }, `Approaching listener limit for event "${eventName}"`);
|
|
409
166
|
}
|
|
410
167
|
if (this._totalListeners >= totalThreshold) {
|
|
411
|
-
logger.warn({ current: this._totalListeners, max: this._maxTotalListeners },
|
|
168
|
+
logger.warn({ current: this._totalListeners, max: this._maxTotalListeners }, 'Approaching total listener limit');
|
|
412
169
|
}
|
|
413
170
|
}
|
|
414
171
|
/**
|
|
@@ -441,28 +198,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
441
198
|
};
|
|
442
199
|
}
|
|
443
200
|
/**
|
|
444
|
-
* Get the
|
|
445
|
-
*/
|
|
446
|
-
get decayRate() {
|
|
447
|
-
return this._decayRate;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Get the decay interval in milliseconds
|
|
451
|
-
*/
|
|
452
|
-
get decayIntervalMs() {
|
|
453
|
-
return this._decayIntervalMs;
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Get the failure threshold
|
|
457
|
-
*/
|
|
458
|
-
get failureThreshold() {
|
|
459
|
-
return this._failureThreshold;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Get the accelerated decay multiplier
|
|
201
|
+
* Get the decay check interval in milliseconds
|
|
463
202
|
*/
|
|
464
|
-
get
|
|
465
|
-
return this.
|
|
203
|
+
get decayCheckIntervalMs() {
|
|
204
|
+
return this._decayCheckIntervalMs;
|
|
466
205
|
}
|
|
467
206
|
/**
|
|
468
207
|
* Get the persistence provider
|
|
@@ -493,14 +232,14 @@ export class TrustEngine extends EventEmitter {
|
|
|
493
232
|
*/
|
|
494
233
|
async loadFromPersistence() {
|
|
495
234
|
if (!this._persistence) {
|
|
496
|
-
throw new Error(
|
|
235
|
+
throw new Error('No persistence provider configured');
|
|
497
236
|
}
|
|
498
237
|
const records = await this._persistence.query();
|
|
499
238
|
this.records.clear();
|
|
500
239
|
for (const record of records) {
|
|
501
240
|
this.records.set(record.entityId, record);
|
|
502
241
|
}
|
|
503
|
-
logger.info({ count: records.length },
|
|
242
|
+
logger.info({ count: records.length }, 'Loaded trust records from persistence');
|
|
504
243
|
return records.length;
|
|
505
244
|
}
|
|
506
245
|
/**
|
|
@@ -508,14 +247,14 @@ export class TrustEngine extends EventEmitter {
|
|
|
508
247
|
*/
|
|
509
248
|
async saveToPersistence() {
|
|
510
249
|
if (!this._persistence) {
|
|
511
|
-
throw new Error(
|
|
250
|
+
throw new Error('No persistence provider configured');
|
|
512
251
|
}
|
|
513
252
|
let count = 0;
|
|
514
253
|
for (const record of this.records.values()) {
|
|
515
254
|
await this._persistence.save(record);
|
|
516
255
|
count++;
|
|
517
256
|
}
|
|
518
|
-
logger.info({ count },
|
|
257
|
+
logger.info({ count }, 'Saved trust records to persistence');
|
|
519
258
|
return count;
|
|
520
259
|
}
|
|
521
260
|
/**
|
|
@@ -540,8 +279,8 @@ export class TrustEngine extends EventEmitter {
|
|
|
540
279
|
*/
|
|
541
280
|
emitTrustEvent(event) {
|
|
542
281
|
this.emit(event.type, event);
|
|
543
|
-
this.emit(
|
|
544
|
-
logger.debug({ event },
|
|
282
|
+
this.emit('trust:*', event); // Wildcard for all events
|
|
283
|
+
logger.debug({ event }, 'Trust event emitted');
|
|
545
284
|
}
|
|
546
285
|
/**
|
|
547
286
|
* Calculate trust score for an entity
|
|
@@ -549,41 +288,34 @@ export class TrustEngine extends EventEmitter {
|
|
|
549
288
|
async calculate(entityId) {
|
|
550
289
|
const record = this.records.get(entityId);
|
|
551
290
|
const signals = record?.signals ?? [];
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
291
|
+
// Calculate factor scores (16-factor model)
|
|
292
|
+
const factorScores = this.calculateFactorScores(signals);
|
|
293
|
+
// Calculate weighted total using factor weights
|
|
294
|
+
let score = 0;
|
|
295
|
+
for (const code of FACTOR_CODES) {
|
|
296
|
+
score += factorScores[code] * FACTOR_WEIGHTS[code] * 1000;
|
|
297
|
+
}
|
|
298
|
+
score = Math.round(score);
|
|
299
|
+
// Guard: if factor arithmetic produced NaN/Infinity, fall back to 0
|
|
300
|
+
if (!Number.isFinite(score)) {
|
|
301
|
+
logger.warn({ entityId, score }, 'Score computation produced non-finite value, defaulting to 0');
|
|
302
|
+
score = 0;
|
|
303
|
+
}
|
|
560
304
|
// Clamp to valid range
|
|
561
305
|
const clampedScore = Math.max(0, Math.min(1000, score));
|
|
562
306
|
const level = this.scoreToLevel(clampedScore);
|
|
307
|
+
// Backwards compat: also compute legacy 4-bucket components
|
|
308
|
+
const components = this.calculateComponents(signals);
|
|
563
309
|
const factors = this.getSignificantFactors(components);
|
|
564
|
-
logger.debug({ entityId, score: clampedScore, level,
|
|
310
|
+
logger.debug({ entityId, score: clampedScore, level, factorScores }, 'Trust calculated');
|
|
565
311
|
return {
|
|
566
312
|
score: clampedScore,
|
|
567
313
|
level,
|
|
568
314
|
components,
|
|
315
|
+
factorScores,
|
|
569
316
|
factors,
|
|
570
317
|
};
|
|
571
318
|
}
|
|
572
|
-
/**
|
|
573
|
-
* Check if an entity has accelerated decay active
|
|
574
|
-
*/
|
|
575
|
-
hasAcceleratedDecay(record) {
|
|
576
|
-
const now = Date.now();
|
|
577
|
-
const recentFailures = record.recentFailures.filter((timestamp) => now - new Date(timestamp).getTime() < this._failureWindowMs);
|
|
578
|
-
return recentFailures.length >= this._minFailuresForAcceleration;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Clean up old failure timestamps outside the window
|
|
582
|
-
*/
|
|
583
|
-
cleanupFailures(record) {
|
|
584
|
-
const now = Date.now();
|
|
585
|
-
record.recentFailures = record.recentFailures.filter((timestamp) => now - new Date(timestamp).getTime() < this._failureWindowMs);
|
|
586
|
-
}
|
|
587
319
|
/**
|
|
588
320
|
* Clean up old success timestamps outside the window
|
|
589
321
|
*/
|
|
@@ -628,7 +360,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
628
360
|
record.lastCalculatedAt = new Date().toISOString();
|
|
629
361
|
// Emit recovery event
|
|
630
362
|
this.emitTrustEvent({
|
|
631
|
-
type:
|
|
363
|
+
type: 'trust:recovery_applied',
|
|
632
364
|
entityId: record.entityId,
|
|
633
365
|
timestamp: new Date().toISOString(),
|
|
634
366
|
signal,
|
|
@@ -641,10 +373,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
641
373
|
// Check for milestones
|
|
642
374
|
if (previousLevel !== record.level && record.level > previousLevel) {
|
|
643
375
|
this.emitTrustEvent({
|
|
644
|
-
type:
|
|
376
|
+
type: 'trust:recovery_milestone',
|
|
645
377
|
entityId: record.entityId,
|
|
646
378
|
timestamp: new Date().toISOString(),
|
|
647
|
-
milestone:
|
|
379
|
+
milestone: 'tier_restored',
|
|
648
380
|
previousScore,
|
|
649
381
|
newScore: record.score,
|
|
650
382
|
details: `Promoted from ${TRUST_LEVEL_NAMES[previousLevel]} to ${TRUST_LEVEL_NAMES[record.level]}`,
|
|
@@ -653,10 +385,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
653
385
|
// Check if accelerated recovery was just earned
|
|
654
386
|
if (record.consecutiveSuccesses === this._minSuccessesForAcceleration) {
|
|
655
387
|
this.emitTrustEvent({
|
|
656
|
-
type:
|
|
388
|
+
type: 'trust:recovery_milestone',
|
|
657
389
|
entityId: record.entityId,
|
|
658
390
|
timestamp: new Date().toISOString(),
|
|
659
|
-
milestone:
|
|
391
|
+
milestone: 'accelerated_recovery_earned',
|
|
660
392
|
previousScore,
|
|
661
393
|
newScore: record.score,
|
|
662
394
|
details: `Earned accelerated recovery after ${this._minSuccessesForAcceleration} consecutive successes`,
|
|
@@ -665,10 +397,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
665
397
|
// Check for full recovery
|
|
666
398
|
if (record.score >= record.peakScore && previousScore < record.peakScore) {
|
|
667
399
|
this.emitTrustEvent({
|
|
668
|
-
type:
|
|
400
|
+
type: 'trust:recovery_milestone',
|
|
669
401
|
entityId: record.entityId,
|
|
670
402
|
timestamp: new Date().toISOString(),
|
|
671
|
-
milestone:
|
|
403
|
+
milestone: 'full_recovery',
|
|
672
404
|
previousScore,
|
|
673
405
|
newScore: record.score,
|
|
674
406
|
details: `Fully recovered to peak score of ${record.peakScore}`,
|
|
@@ -680,7 +412,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
680
412
|
newScore: record.score,
|
|
681
413
|
recoveryAmount,
|
|
682
414
|
accelerated,
|
|
683
|
-
},
|
|
415
|
+
}, 'Trust recovery applied');
|
|
684
416
|
}
|
|
685
417
|
/**
|
|
686
418
|
* Get trust score for an entity (with automatic decay)
|
|
@@ -688,25 +420,14 @@ export class TrustEngine extends EventEmitter {
|
|
|
688
420
|
async getScore(entityId) {
|
|
689
421
|
const record = this.records.get(entityId);
|
|
690
422
|
if (record) {
|
|
691
|
-
//
|
|
692
|
-
this.cleanupFailures(record);
|
|
693
|
-
if (this._readinessMode === "checkpoint_schedule") {
|
|
694
|
-
await this.applyScheduledReadinessAdjustment(record);
|
|
695
|
-
return record;
|
|
696
|
-
}
|
|
697
|
-
// Apply decay if stale
|
|
423
|
+
// Apply decay if enough time has passed since last check
|
|
698
424
|
const staleness = Date.now() - new Date(record.lastCalculatedAt).getTime();
|
|
699
|
-
if (staleness > this.
|
|
425
|
+
if (staleness > this._decayCheckIntervalMs) {
|
|
700
426
|
const previousScore = record.score;
|
|
701
427
|
const previousLevel = record.level;
|
|
702
|
-
//
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
? this._decayRate * this._acceleratedDecayMultiplier
|
|
706
|
-
: this._decayRate;
|
|
707
|
-
// Apply decay based on staleness
|
|
708
|
-
const decayPeriods = Math.floor(staleness / this._decayIntervalMs);
|
|
709
|
-
const decayMultiplier = Math.pow(1 - effectiveDecayRate, decayPeriods);
|
|
428
|
+
// Milestone-based decay: convert staleness to days, apply stepped multiplier
|
|
429
|
+
const daysSinceActivity = staleness / (24 * 60 * 60 * 1000);
|
|
430
|
+
const decayMultiplier = calculateDecayMultiplier(daysSinceActivity);
|
|
710
431
|
const decayedScore = Math.round(record.score * decayMultiplier);
|
|
711
432
|
const clampedScore = Math.max(0, decayedScore);
|
|
712
433
|
record.score = clampedScore;
|
|
@@ -715,26 +436,25 @@ export class TrustEngine extends EventEmitter {
|
|
|
715
436
|
// Emit decay event
|
|
716
437
|
if (previousScore !== record.score) {
|
|
717
438
|
this.emitTrustEvent({
|
|
718
|
-
type:
|
|
439
|
+
type: 'trust:decay_applied',
|
|
719
440
|
entityId,
|
|
720
441
|
timestamp: new Date().toISOString(),
|
|
721
442
|
previousScore,
|
|
722
443
|
newScore: record.score,
|
|
723
444
|
decayAmount: previousScore - record.score,
|
|
724
445
|
stalenessMs: staleness,
|
|
725
|
-
accelerated,
|
|
726
446
|
});
|
|
727
447
|
// Emit tier change if applicable
|
|
728
448
|
if (previousLevel !== record.level) {
|
|
729
449
|
this.emitTrustEvent({
|
|
730
|
-
type:
|
|
450
|
+
type: 'trust:tier_changed',
|
|
731
451
|
entityId,
|
|
732
452
|
timestamp: new Date().toISOString(),
|
|
733
453
|
previousLevel,
|
|
734
454
|
newLevel: record.level,
|
|
735
455
|
previousLevelName: TRUST_LEVEL_NAMES[previousLevel],
|
|
736
456
|
newLevelName: TRUST_LEVEL_NAMES[record.level],
|
|
737
|
-
direction: record.level < previousLevel ?
|
|
457
|
+
direction: record.level < previousLevel ? 'demoted' : 'promoted',
|
|
738
458
|
});
|
|
739
459
|
}
|
|
740
460
|
// Auto-persist after decay
|
|
@@ -744,10 +464,35 @@ export class TrustEngine extends EventEmitter {
|
|
|
744
464
|
}
|
|
745
465
|
return record;
|
|
746
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Validate a trust signal before ingestion.
|
|
469
|
+
* Rejects NaN, Infinity, out-of-range values, and missing required fields.
|
|
470
|
+
*
|
|
471
|
+
* @throws Error on invalid signal
|
|
472
|
+
*/
|
|
473
|
+
validateSignal(signal) {
|
|
474
|
+
if (!signal) {
|
|
475
|
+
throw new Error('Signal is required');
|
|
476
|
+
}
|
|
477
|
+
if (!signal.entityId) {
|
|
478
|
+
throw new Error('Signal must have an entityId');
|
|
479
|
+
}
|
|
480
|
+
if (!signal.type || typeof signal.type !== 'string') {
|
|
481
|
+
throw new Error('Signal must have a non-empty string type');
|
|
482
|
+
}
|
|
483
|
+
if (!Number.isFinite(signal.value)) {
|
|
484
|
+
throw new Error(`Invalid signal value: ${signal.value} (must be a finite number)`);
|
|
485
|
+
}
|
|
486
|
+
if (signal.value < 0 || signal.value > 1) {
|
|
487
|
+
throw new Error(`Signal value out of range: ${signal.value} (must be 0.0–1.0)`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
747
490
|
/**
|
|
748
491
|
* Record a trust signal
|
|
492
|
+
* @throws Error if signal is invalid (NaN, out of range, missing fields)
|
|
749
493
|
*/
|
|
750
494
|
async recordSignal(signal) {
|
|
495
|
+
this.validateSignal(signal);
|
|
751
496
|
let record = this.records.get(signal.entityId);
|
|
752
497
|
let isNewEntity = false;
|
|
753
498
|
if (!record) {
|
|
@@ -757,28 +502,10 @@ export class TrustEngine extends EventEmitter {
|
|
|
757
502
|
}
|
|
758
503
|
const previousScore = record.score;
|
|
759
504
|
const previousLevel = record.level;
|
|
760
|
-
//
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
this.cleanupFailures(record);
|
|
764
|
-
// Reset consecutive successes on failure
|
|
505
|
+
// Reset consecutive successes on low-value signals (below 0.3)
|
|
506
|
+
// Neutral signals (0.3-0.7) don't break the success streak
|
|
507
|
+
if (signal.value < 0.3) {
|
|
765
508
|
record.consecutiveSuccesses = 0;
|
|
766
|
-
const acceleratedDecayActive = this.hasAcceleratedDecay(record);
|
|
767
|
-
this.emitTrustEvent({
|
|
768
|
-
type: "trust:failure_detected",
|
|
769
|
-
entityId: signal.entityId,
|
|
770
|
-
timestamp: new Date().toISOString(),
|
|
771
|
-
signal,
|
|
772
|
-
failureCount: record.recentFailures.length,
|
|
773
|
-
acceleratedDecayActive,
|
|
774
|
-
});
|
|
775
|
-
logger.warn({
|
|
776
|
-
entityId: signal.entityId,
|
|
777
|
-
signalType: signal.type,
|
|
778
|
-
signalValue: signal.value,
|
|
779
|
-
failureCount: record.recentFailures.length,
|
|
780
|
-
acceleratedDecayActive,
|
|
781
|
-
}, "Failure signal detected");
|
|
782
509
|
}
|
|
783
510
|
// Detect success signals and apply recovery
|
|
784
511
|
if (signal.value >= this._successThreshold) {
|
|
@@ -796,7 +523,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
796
523
|
signalValue: signal.value,
|
|
797
524
|
consecutiveSuccesses: record.consecutiveSuccesses,
|
|
798
525
|
recoveryAmount,
|
|
799
|
-
},
|
|
526
|
+
}, 'Success signal detected');
|
|
800
527
|
}
|
|
801
528
|
// Add signal
|
|
802
529
|
record.signals.push(signal);
|
|
@@ -810,13 +537,8 @@ export class TrustEngine extends EventEmitter {
|
|
|
810
537
|
record.score = calculation.score;
|
|
811
538
|
record.level = calculation.level;
|
|
812
539
|
record.components = calculation.components;
|
|
540
|
+
record.factorScores = calculation.factorScores;
|
|
813
541
|
record.lastCalculatedAt = new Date().toISOString();
|
|
814
|
-
record.readinessCheckpointIndex = 0;
|
|
815
|
-
record.deferredReadinessMultiplier = 1;
|
|
816
|
-
record.readinessBaselineScore = calculation.score;
|
|
817
|
-
record.freshnessCheckpointIndex = 0;
|
|
818
|
-
record.deferredFreshnessMultiplier = 1;
|
|
819
|
-
record.freshnessBaselineScore = calculation.score;
|
|
820
542
|
// Record history if significant change
|
|
821
543
|
if (Math.abs(calculation.score - previousScore) >= 10) {
|
|
822
544
|
record.history.push({
|
|
@@ -832,7 +554,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
832
554
|
}
|
|
833
555
|
// Emit signal recorded event
|
|
834
556
|
this.emitTrustEvent({
|
|
835
|
-
type:
|
|
557
|
+
type: 'trust:signal_recorded',
|
|
836
558
|
entityId: signal.entityId,
|
|
837
559
|
timestamp: new Date().toISOString(),
|
|
838
560
|
signal,
|
|
@@ -842,7 +564,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
842
564
|
// Emit score changed event if significant
|
|
843
565
|
if (Math.abs(calculation.score - previousScore) >= 5) {
|
|
844
566
|
this.emitTrustEvent({
|
|
845
|
-
type:
|
|
567
|
+
type: 'trust:score_changed',
|
|
846
568
|
entityId: signal.entityId,
|
|
847
569
|
timestamp: new Date().toISOString(),
|
|
848
570
|
previousScore,
|
|
@@ -854,14 +576,14 @@ export class TrustEngine extends EventEmitter {
|
|
|
854
576
|
// Emit tier changed event if applicable
|
|
855
577
|
if (previousLevel !== calculation.level && !isNewEntity) {
|
|
856
578
|
this.emitTrustEvent({
|
|
857
|
-
type:
|
|
579
|
+
type: 'trust:tier_changed',
|
|
858
580
|
entityId: signal.entityId,
|
|
859
581
|
timestamp: new Date().toISOString(),
|
|
860
582
|
previousLevel,
|
|
861
583
|
newLevel: calculation.level,
|
|
862
584
|
previousLevelName: TRUST_LEVEL_NAMES[previousLevel],
|
|
863
585
|
newLevelName: TRUST_LEVEL_NAMES[calculation.level],
|
|
864
|
-
direction: calculation.level > previousLevel ?
|
|
586
|
+
direction: calculation.level > previousLevel ? 'promoted' : 'demoted',
|
|
865
587
|
});
|
|
866
588
|
}
|
|
867
589
|
// Auto-persist if enabled
|
|
@@ -870,13 +592,18 @@ export class TrustEngine extends EventEmitter {
|
|
|
870
592
|
entityId: signal.entityId,
|
|
871
593
|
signalType: signal.type,
|
|
872
594
|
newScore: calculation.score,
|
|
873
|
-
},
|
|
595
|
+
}, 'Signal recorded');
|
|
874
596
|
}
|
|
875
597
|
/**
|
|
876
598
|
* Initialize trust for a new entity
|
|
877
599
|
*/
|
|
878
600
|
async initializeEntity(entityId, initialLevel = 1) {
|
|
879
601
|
const score = TRUST_THRESHOLDS[initialLevel].min;
|
|
602
|
+
// Initialize all 16 factors to 0.5
|
|
603
|
+
const initialFactorScores = {};
|
|
604
|
+
for (const code of FACTOR_CODES) {
|
|
605
|
+
initialFactorScores[code] = 0.5;
|
|
606
|
+
}
|
|
880
607
|
const record = {
|
|
881
608
|
entityId,
|
|
882
609
|
score,
|
|
@@ -887,39 +614,33 @@ export class TrustEngine extends EventEmitter {
|
|
|
887
614
|
identity: 0.5,
|
|
888
615
|
context: 0.5,
|
|
889
616
|
},
|
|
617
|
+
factorScores: initialFactorScores,
|
|
890
618
|
signals: [],
|
|
891
619
|
lastCalculatedAt: new Date().toISOString(),
|
|
892
620
|
history: [
|
|
893
621
|
{
|
|
894
622
|
score,
|
|
895
623
|
level: initialLevel,
|
|
896
|
-
reason:
|
|
624
|
+
reason: 'Initial registration',
|
|
897
625
|
timestamp: new Date().toISOString(),
|
|
898
626
|
},
|
|
899
627
|
],
|
|
900
|
-
recentFailures: [],
|
|
901
628
|
recentSuccesses: [],
|
|
902
629
|
peakScore: score,
|
|
903
630
|
consecutiveSuccesses: 0,
|
|
904
|
-
readinessCheckpointIndex: 0,
|
|
905
|
-
deferredReadinessMultiplier: 1,
|
|
906
|
-
readinessBaselineScore: score,
|
|
907
|
-
freshnessCheckpointIndex: 0,
|
|
908
|
-
deferredFreshnessMultiplier: 1,
|
|
909
|
-
freshnessBaselineScore: score,
|
|
910
631
|
};
|
|
911
632
|
this.records.set(entityId, record);
|
|
912
633
|
// Auto-persist if enabled
|
|
913
634
|
await this.autoPersistRecord(record);
|
|
914
635
|
// Emit initialized event
|
|
915
636
|
this.emitTrustEvent({
|
|
916
|
-
type:
|
|
637
|
+
type: 'trust:initialized',
|
|
917
638
|
entityId,
|
|
918
639
|
timestamp: new Date().toISOString(),
|
|
919
640
|
initialScore: score,
|
|
920
641
|
initialLevel,
|
|
921
642
|
});
|
|
922
|
-
logger.info({ entityId, initialLevel },
|
|
643
|
+
logger.info({ entityId, initialLevel }, 'Entity trust initialized');
|
|
923
644
|
return record;
|
|
924
645
|
}
|
|
925
646
|
/**
|
|
@@ -934,6 +655,76 @@ export class TrustEngine extends EventEmitter {
|
|
|
934
655
|
getLevelName(level) {
|
|
935
656
|
return TRUST_LEVEL_NAMES[level];
|
|
936
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Generate a human-readable explanation of an entity's current trust score.
|
|
660
|
+
*
|
|
661
|
+
* Provides full transparency into how the score was computed, including
|
|
662
|
+
* per-factor contributions, decay status, and what's needed to advance.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* const explanation = await engine.explainScore('agent-123');
|
|
667
|
+
* console.log(`Score: ${explanation.score} (${explanation.levelName})`);
|
|
668
|
+
* console.log(`Top factor: ${explanation.topPositiveFactors[0]}`);
|
|
669
|
+
* console.log(`Points to next tier: ${explanation.pointsToNextLevel}`);
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
async explainScore(entityId) {
|
|
673
|
+
const record = this.records.get(entityId);
|
|
674
|
+
if (!record) {
|
|
675
|
+
throw new Error(`Entity not found: ${entityId}`);
|
|
676
|
+
}
|
|
677
|
+
const signals = record.signals ?? [];
|
|
678
|
+
const factorScores = this.calculateFactorScores(signals);
|
|
679
|
+
// Build factor breakdown
|
|
680
|
+
const factorBreakdown = FACTOR_CODES.map((code) => {
|
|
681
|
+
const weight = FACTOR_WEIGHTS[code];
|
|
682
|
+
const rawScore = factorScores[code];
|
|
683
|
+
const contribution = Math.round(rawScore * weight * 1000 * 100) / 100;
|
|
684
|
+
return { code, weight, rawScore, contribution };
|
|
685
|
+
});
|
|
686
|
+
// Sort for top positive/negative
|
|
687
|
+
const sorted = [...factorBreakdown].sort((a, b) => b.contribution - a.contribution);
|
|
688
|
+
const midpoint = 0.5; // Neutral factor score
|
|
689
|
+
const topPositiveFactors = sorted
|
|
690
|
+
.filter((f) => f.rawScore > midpoint)
|
|
691
|
+
.slice(0, 5)
|
|
692
|
+
.map((f) => f.code);
|
|
693
|
+
const topNegativeFactors = sorted
|
|
694
|
+
.filter((f) => f.rawScore < midpoint)
|
|
695
|
+
.sort((a, b) => a.contribution - b.contribution)
|
|
696
|
+
.slice(0, 5)
|
|
697
|
+
.map((f) => f.code);
|
|
698
|
+
// Calculate decay
|
|
699
|
+
const lastSignalTime = signals.length > 0
|
|
700
|
+
? Math.max(...signals.map((s) => new Date(s.timestamp).getTime()))
|
|
701
|
+
: null;
|
|
702
|
+
const daysSinceLastSignal = lastSignalTime !== null
|
|
703
|
+
? (Date.now() - lastSignalTime) / (24 * 60 * 60 * 1000)
|
|
704
|
+
: null;
|
|
705
|
+
const decayMultiplier = daysSinceLastSignal !== null
|
|
706
|
+
? calculateDecayMultiplier(daysSinceLastSignal)
|
|
707
|
+
: 1.0;
|
|
708
|
+
// Points to next level
|
|
709
|
+
const currentLevel = record.level;
|
|
710
|
+
const nextLevelThreshold = currentLevel < 7 ? TRUST_THRESHOLDS[(currentLevel + 1)] : null;
|
|
711
|
+
const pointsToNextLevel = nextLevelThreshold !== null ? nextLevelThreshold.min - record.score : null;
|
|
712
|
+
return {
|
|
713
|
+
entityId,
|
|
714
|
+
score: record.score,
|
|
715
|
+
level: currentLevel,
|
|
716
|
+
levelName: TRUST_LEVEL_NAMES[currentLevel],
|
|
717
|
+
levelRange: TRUST_THRESHOLDS[currentLevel],
|
|
718
|
+
pointsToNextLevel: pointsToNextLevel !== null && pointsToNextLevel > 0 ? pointsToNextLevel : null,
|
|
719
|
+
signalCount: signals.length,
|
|
720
|
+
factorBreakdown,
|
|
721
|
+
topPositiveFactors,
|
|
722
|
+
topNegativeFactors,
|
|
723
|
+
decayMultiplier: Math.round(decayMultiplier * 1000) / 1000,
|
|
724
|
+
daysSinceLastSignal: daysSinceLastSignal !== null ? Math.round(daysSinceLastSignal * 100) / 100 : null,
|
|
725
|
+
generatedAt: new Date().toISOString(),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
937
728
|
/**
|
|
938
729
|
* Convert score to trust level
|
|
939
730
|
*/
|
|
@@ -946,25 +737,61 @@ export class TrustEngine extends EventEmitter {
|
|
|
946
737
|
return 0;
|
|
947
738
|
}
|
|
948
739
|
/**
|
|
740
|
+
* @deprecated Use calculateFactorScores for 16-factor model. Kept for backwards compatibility.
|
|
949
741
|
* Calculate component scores from signals
|
|
950
742
|
*/
|
|
951
|
-
calculateComponents(signals
|
|
743
|
+
calculateComponents(signals) {
|
|
952
744
|
// Group signals by type
|
|
953
|
-
const behavioral = signals.filter((s) => s.type.startsWith(
|
|
954
|
-
const compliance = signals.filter((s) => s.type.startsWith(
|
|
955
|
-
const identity = signals.filter((s) => s.type.startsWith(
|
|
956
|
-
const context = signals.filter((s) => s.type.startsWith(
|
|
745
|
+
const behavioral = signals.filter((s) => s.type.startsWith('behavioral.'));
|
|
746
|
+
const compliance = signals.filter((s) => s.type.startsWith('compliance.'));
|
|
747
|
+
const identity = signals.filter((s) => s.type.startsWith('identity.'));
|
|
748
|
+
const context = signals.filter((s) => s.type.startsWith('context.'));
|
|
957
749
|
return {
|
|
958
|
-
behavioral: this.averageSignalValue(behavioral, 0.5
|
|
959
|
-
compliance: this.averageSignalValue(compliance, 0.5
|
|
960
|
-
identity: this.averageSignalValue(identity, 0.5
|
|
961
|
-
context: this.averageSignalValue(context, 0.5
|
|
750
|
+
behavioral: this.averageSignalValue(behavioral, 0.5),
|
|
751
|
+
compliance: this.averageSignalValue(compliance, 0.5),
|
|
752
|
+
identity: this.averageSignalValue(identity, 0.5),
|
|
753
|
+
context: this.averageSignalValue(context, 0.5),
|
|
962
754
|
};
|
|
963
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Calculate per-factor scores from signals.
|
|
758
|
+
* Signals can use either:
|
|
759
|
+
* - Factor code prefix (e.g. 'CT-COMP.success')
|
|
760
|
+
* - Legacy bucket prefix (e.g. 'behavioral.success') — mapped to factors via SIGNAL_PREFIX_TO_FACTORS
|
|
761
|
+
*/
|
|
762
|
+
calculateFactorScores(signals) {
|
|
763
|
+
const factorSignals = {};
|
|
764
|
+
// Initialize all factors
|
|
765
|
+
for (const code of FACTOR_CODES) {
|
|
766
|
+
factorSignals[code] = [];
|
|
767
|
+
}
|
|
768
|
+
for (const signal of signals) {
|
|
769
|
+
const prefix = signal.type.split('.')[0];
|
|
770
|
+
// Check if it's a direct factor code
|
|
771
|
+
if (FACTOR_CODES.includes(prefix)) {
|
|
772
|
+
factorSignals[prefix].push(signal);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
// Check if it's a legacy bucket prefix
|
|
776
|
+
const mappedFactors = SIGNAL_PREFIX_TO_FACTORS[prefix];
|
|
777
|
+
if (mappedFactors) {
|
|
778
|
+
// Distribute signal across mapped factors
|
|
779
|
+
for (const factorCode of mappedFactors) {
|
|
780
|
+
factorSignals[factorCode].push(signal);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Calculate average score for each factor
|
|
785
|
+
const scores = {};
|
|
786
|
+
for (const code of FACTOR_CODES) {
|
|
787
|
+
scores[code] = this.averageSignalValue(factorSignals[code], 0.5);
|
|
788
|
+
}
|
|
789
|
+
return scores;
|
|
790
|
+
}
|
|
964
791
|
/**
|
|
965
792
|
* Calculate average signal value with default
|
|
966
793
|
*/
|
|
967
|
-
averageSignalValue(signals, defaultValue
|
|
794
|
+
averageSignalValue(signals, defaultValue) {
|
|
968
795
|
if (signals.length === 0)
|
|
969
796
|
return defaultValue;
|
|
970
797
|
// Weight recent signals more heavily
|
|
@@ -974,66 +801,27 @@ export class TrustEngine extends EventEmitter {
|
|
|
974
801
|
for (const signal of signals) {
|
|
975
802
|
const age = now - new Date(signal.timestamp).getTime();
|
|
976
803
|
const weight = Math.exp(-age / (7 * 24 * 60 * 60 * 1000)); // 7-day half-life
|
|
977
|
-
|
|
978
|
-
weightedSum += adjustedValue * weight;
|
|
804
|
+
weightedSum += signal.value * weight;
|
|
979
805
|
totalWeight += weight;
|
|
980
806
|
}
|
|
981
807
|
return totalWeight > 0 ? weightedSum / totalWeight : defaultValue;
|
|
982
808
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Human/manual positive approvals carry less upward impact at higher tiers.
|
|
985
|
-
*/
|
|
986
|
-
adjustSignalValueForTier(signal, defaultValue, currentLevel) {
|
|
987
|
-
if (!this.isHumanApprovalSignal(signal) || signal.value <= defaultValue) {
|
|
988
|
-
return signal.value;
|
|
989
|
-
}
|
|
990
|
-
const assistFactor = this.getHumanApprovalAssistFactor(currentLevel);
|
|
991
|
-
return defaultValue + (signal.value - defaultValue) * assistFactor;
|
|
992
|
-
}
|
|
993
|
-
isHumanApprovalSignal(signal) {
|
|
994
|
-
const source = signal.source?.toLowerCase() ?? "";
|
|
995
|
-
return (source === "manual" ||
|
|
996
|
-
source === "human" ||
|
|
997
|
-
source === "human_review" ||
|
|
998
|
-
source === "human-approval");
|
|
999
|
-
}
|
|
1000
|
-
getHumanApprovalAssistFactor(level) {
|
|
1001
|
-
switch (level) {
|
|
1002
|
-
case 0:
|
|
1003
|
-
case 1:
|
|
1004
|
-
return 1.0;
|
|
1005
|
-
case 2:
|
|
1006
|
-
return 0.85;
|
|
1007
|
-
case 3:
|
|
1008
|
-
return 0.7;
|
|
1009
|
-
case 4:
|
|
1010
|
-
return 0.55;
|
|
1011
|
-
case 5:
|
|
1012
|
-
return 0.4;
|
|
1013
|
-
case 6:
|
|
1014
|
-
return 0.3;
|
|
1015
|
-
case 7:
|
|
1016
|
-
return 0.2;
|
|
1017
|
-
default:
|
|
1018
|
-
return 1.0;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
809
|
/**
|
|
1022
810
|
* Get significant factors affecting the score
|
|
1023
811
|
*/
|
|
1024
812
|
getSignificantFactors(components) {
|
|
1025
813
|
const factors = [];
|
|
1026
814
|
if (components.behavioral < 0.3) {
|
|
1027
|
-
factors.push(
|
|
815
|
+
factors.push('Low behavioral trust');
|
|
1028
816
|
}
|
|
1029
817
|
if (components.compliance < 0.3) {
|
|
1030
|
-
factors.push(
|
|
818
|
+
factors.push('Low compliance score');
|
|
1031
819
|
}
|
|
1032
820
|
if (components.identity < 0.3) {
|
|
1033
|
-
factors.push(
|
|
821
|
+
factors.push('Weak identity verification');
|
|
1034
822
|
}
|
|
1035
823
|
if (components.context < 0.3) {
|
|
1036
|
-
factors.push(
|
|
824
|
+
factors.push('Unusual context signals');
|
|
1037
825
|
}
|
|
1038
826
|
return factors;
|
|
1039
827
|
}
|
|
@@ -1042,6 +830,11 @@ export class TrustEngine extends EventEmitter {
|
|
|
1042
830
|
*/
|
|
1043
831
|
createInitialRecord(entityId) {
|
|
1044
832
|
const initialScore = TRUST_THRESHOLDS[1].min;
|
|
833
|
+
// Initialize all 16 factors to 0.5
|
|
834
|
+
const initialFactorScores = {};
|
|
835
|
+
for (const code of FACTOR_CODES) {
|
|
836
|
+
initialFactorScores[code] = 0.5;
|
|
837
|
+
}
|
|
1045
838
|
return {
|
|
1046
839
|
entityId,
|
|
1047
840
|
score: initialScore, // Start at L1 (Provisional) minimum
|
|
@@ -1052,84 +845,15 @@ export class TrustEngine extends EventEmitter {
|
|
|
1052
845
|
identity: 0.5,
|
|
1053
846
|
context: 0.5,
|
|
1054
847
|
},
|
|
848
|
+
factorScores: initialFactorScores,
|
|
1055
849
|
signals: [],
|
|
1056
850
|
lastCalculatedAt: new Date().toISOString(),
|
|
1057
851
|
history: [],
|
|
1058
|
-
recentFailures: [],
|
|
1059
852
|
recentSuccesses: [],
|
|
1060
853
|
peakScore: initialScore,
|
|
1061
854
|
consecutiveSuccesses: 0,
|
|
1062
|
-
readinessCheckpointIndex: 0,
|
|
1063
|
-
deferredReadinessMultiplier: 1,
|
|
1064
|
-
readinessBaselineScore: initialScore,
|
|
1065
|
-
freshnessCheckpointIndex: 0,
|
|
1066
|
-
deferredFreshnessMultiplier: 1,
|
|
1067
|
-
freshnessBaselineScore: initialScore,
|
|
1068
855
|
};
|
|
1069
856
|
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Configure a time-bound Readiness Degree exception for an entity.
|
|
1072
|
-
*/
|
|
1073
|
-
setReadinessException(entityId, options) {
|
|
1074
|
-
const record = this.records.get(entityId);
|
|
1075
|
-
if (!record) {
|
|
1076
|
-
throw new Error(`Entity not found: ${entityId}`);
|
|
1077
|
-
}
|
|
1078
|
-
this.validateReadinessExceptionOptions(options);
|
|
1079
|
-
this.ensureReadinessState(record);
|
|
1080
|
-
record.readinessException = {
|
|
1081
|
-
reason: options.reason,
|
|
1082
|
-
issuedAt: new Date().toISOString(),
|
|
1083
|
-
expiresAt: options.expiresAt,
|
|
1084
|
-
reductionScale: clampReductionScale(options.reductionScale),
|
|
1085
|
-
};
|
|
1086
|
-
record.freshnessException = record.readinessException;
|
|
1087
|
-
}
|
|
1088
|
-
/**
|
|
1089
|
-
* @deprecated Use setReadinessException.
|
|
1090
|
-
*/
|
|
1091
|
-
setFreshnessException(entityId, options) {
|
|
1092
|
-
this.setReadinessException(entityId, options);
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Clear Readiness Degree exception for an entity.
|
|
1096
|
-
*/
|
|
1097
|
-
clearReadinessException(entityId) {
|
|
1098
|
-
const record = this.records.get(entityId);
|
|
1099
|
-
if (!record) {
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
delete record.readinessException;
|
|
1103
|
-
delete record.freshnessException;
|
|
1104
|
-
}
|
|
1105
|
-
/**
|
|
1106
|
-
* @deprecated Use clearReadinessException.
|
|
1107
|
-
*/
|
|
1108
|
-
clearFreshnessException(entityId) {
|
|
1109
|
-
this.clearReadinessException(entityId);
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Get active Readiness Degree exception for an entity.
|
|
1113
|
-
*/
|
|
1114
|
-
getReadinessException(entityId) {
|
|
1115
|
-
const record = this.records.get(entityId);
|
|
1116
|
-
return record?.readinessException ?? record?.freshnessException;
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* @deprecated Use getReadinessException.
|
|
1120
|
-
*/
|
|
1121
|
-
getFreshnessException(entityId) {
|
|
1122
|
-
return this.getReadinessException(entityId);
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Check if accelerated decay is currently active for an entity
|
|
1126
|
-
*/
|
|
1127
|
-
isAcceleratedDecayActive(entityId) {
|
|
1128
|
-
const record = this.records.get(entityId);
|
|
1129
|
-
if (!record)
|
|
1130
|
-
return false;
|
|
1131
|
-
return this.hasAcceleratedDecay(record);
|
|
1132
|
-
}
|
|
1133
857
|
/**
|
|
1134
858
|
* Check if accelerated recovery is currently active for an entity
|
|
1135
859
|
*/
|
|
@@ -1139,16 +863,6 @@ export class TrustEngine extends EventEmitter {
|
|
|
1139
863
|
return false;
|
|
1140
864
|
return this.hasAcceleratedRecovery(record);
|
|
1141
865
|
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Get current failure count for an entity
|
|
1144
|
-
*/
|
|
1145
|
-
getFailureCount(entityId) {
|
|
1146
|
-
const record = this.records.get(entityId);
|
|
1147
|
-
if (!record)
|
|
1148
|
-
return 0;
|
|
1149
|
-
this.cleanupFailures(record);
|
|
1150
|
-
return record.recentFailures.length;
|
|
1151
|
-
}
|
|
1152
866
|
/**
|
|
1153
867
|
* Get consecutive success count for an entity
|
|
1154
868
|
*/
|