@vorionsys/atsf-core 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/LICENSE +1 -1
- package/README.md +82 -29
- package/dist/adapters/base-adapter.d.ts +94 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +233 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +5 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +83 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +4 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/webhook-handler.d.ts +64 -0
- package/dist/adapters/webhook-handler.d.ts.map +1 -0
- package/dist/adapters/webhook-handler.js +170 -0
- package/dist/adapters/webhook-handler.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +2 -0
- package/dist/api/server.js.map +1 -1
- package/dist/arbitration/index.d.ts +0 -8
- package/dist/arbitration/index.d.ts.map +1 -1
- package/dist/arbitration/index.js +2 -0
- package/dist/arbitration/index.js.map +1 -1
- package/dist/arbitration/types.d.ts.map +1 -1
- package/dist/arbitration/types.js +2 -8
- package/dist/arbitration/types.js.map +1 -1
- package/dist/basis/evaluator.d.ts +0 -5
- package/dist/basis/evaluator.d.ts.map +1 -1
- package/dist/basis/evaluator.js +2 -0
- package/dist/basis/evaluator.js.map +1 -1
- package/dist/basis/index.d.ts.map +1 -1
- package/dist/basis/index.js +2 -0
- package/dist/basis/index.js.map +1 -1
- package/dist/basis/parser.d.ts +28 -28
- package/dist/basis/parser.d.ts.map +1 -1
- package/dist/basis/parser.js +2 -0
- package/dist/basis/parser.js.map +1 -1
- package/dist/basis/types.d.ts.map +1 -1
- package/dist/basis/types.js +2 -3
- package/dist/basis/types.js.map +1 -1
- package/dist/chain/index.d.ts +0 -8
- package/dist/chain/index.d.ts.map +1 -1
- package/dist/chain/index.js +2 -0
- package/dist/chain/index.js.map +1 -1
- package/dist/cognigate/index.d.ts +0 -8
- package/dist/cognigate/index.d.ts.map +1 -1
- package/dist/cognigate/index.js +2 -0
- package/dist/cognigate/index.js.map +1 -1
- package/dist/common/adapters.d.ts.map +1 -1
- package/dist/common/adapters.js +2 -8
- package/dist/common/adapters.js.map +1 -1
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +2 -0
- package/dist/common/config.js.map +1 -1
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +2 -0
- package/dist/common/index.js.map +1 -1
- package/dist/common/logger.d.ts.map +1 -1
- package/dist/common/logger.js +2 -0
- package/dist/common/logger.js.map +1 -1
- package/dist/common/types.d.ts +7 -7
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js +2 -9
- package/dist/common/types.js.map +1 -1
- package/dist/containment/index.d.ts +0 -8
- package/dist/containment/index.d.ts.map +1 -1
- package/dist/containment/index.js +2 -0
- package/dist/containment/index.js.map +1 -1
- package/dist/containment/types.d.ts.map +1 -1
- package/dist/containment/types.js +2 -8
- package/dist/containment/types.js.map +1 -1
- package/dist/contracts/index.d.ts +0 -8
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js +2 -8
- package/dist/contracts/types.js.map +1 -1
- package/dist/crewai/callback.d.ts +0 -7
- package/dist/crewai/callback.d.ts.map +1 -1
- package/dist/crewai/callback.js +2 -0
- package/dist/crewai/callback.js.map +1 -1
- package/dist/crewai/executor.d.ts +0 -7
- package/dist/crewai/executor.d.ts.map +1 -1
- package/dist/crewai/executor.js +2 -0
- package/dist/crewai/executor.js.map +1 -1
- package/dist/crewai/index.d.ts.map +1 -1
- package/dist/crewai/index.js +2 -0
- package/dist/crewai/index.js.map +1 -1
- package/dist/crewai/tools.d.ts.map +1 -1
- package/dist/crewai/tools.js +2 -7
- package/dist/crewai/tools.js.map +1 -1
- package/dist/crewai/types.d.ts.map +1 -1
- package/dist/crewai/types.js +2 -7
- package/dist/crewai/types.js.map +1 -1
- package/dist/enforce/index.d.ts +0 -15
- package/dist/enforce/index.d.ts.map +1 -1
- package/dist/enforce/index.js +3 -1
- package/dist/enforce/index.js.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.d.ts +0 -15
- package/dist/enforce/trust-aware-enforcement-service.d.ts.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.js +2 -0
- package/dist/enforce/trust-aware-enforcement-service.js.map +1 -1
- package/dist/governance/fluid-workflow.d.ts +0 -8
- package/dist/governance/fluid-workflow.d.ts.map +1 -1
- package/dist/governance/fluid-workflow.js +2 -0
- package/dist/governance/fluid-workflow.js.map +1 -1
- package/dist/governance/index.d.ts +0 -8
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -1
- package/dist/governance/proof-bridge.d.ts.map +1 -1
- package/dist/governance/proof-bridge.js +2 -12
- package/dist/governance/proof-bridge.js.map +1 -1
- package/dist/governance/types.d.ts.map +1 -1
- package/dist/governance/types.js +2 -8
- package/dist/governance/types.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/intent/index.d.ts +0 -13
- package/dist/intent/index.d.ts.map +1 -1
- package/dist/intent/index.js +4 -2
- package/dist/intent/index.js.map +1 -1
- package/dist/intent/persistent-intent-service.d.ts +0 -15
- package/dist/intent/persistent-intent-service.d.ts.map +1 -1
- package/dist/intent/persistent-intent-service.js +2 -0
- package/dist/intent/persistent-intent-service.js.map +1 -1
- package/dist/intent/supabase-intent-repository.d.ts +0 -17
- package/dist/intent/supabase-intent-repository.d.ts.map +1 -1
- package/dist/intent/supabase-intent-repository.js +2 -0
- package/dist/intent/supabase-intent-repository.js.map +1 -1
- package/dist/intent-gateway/index.d.ts +499 -0
- package/dist/intent-gateway/index.d.ts.map +1 -0
- package/dist/intent-gateway/index.js +1332 -0
- package/dist/intent-gateway/index.js.map +1 -0
- package/dist/langchain/callback.d.ts +0 -7
- package/dist/langchain/callback.d.ts.map +1 -1
- package/dist/langchain/callback.js +2 -0
- package/dist/langchain/callback.js.map +1 -1
- package/dist/langchain/executor.d.ts +0 -7
- package/dist/langchain/executor.d.ts.map +1 -1
- package/dist/langchain/executor.js +2 -0
- package/dist/langchain/executor.js.map +1 -1
- package/dist/langchain/index.d.ts.map +1 -1
- package/dist/langchain/index.js +2 -0
- package/dist/langchain/index.js.map +1 -1
- package/dist/langchain/tools.d.ts.map +1 -1
- package/dist/langchain/tools.js +2 -7
- package/dist/langchain/tools.js.map +1 -1
- package/dist/langchain/types.d.ts.map +1 -1
- package/dist/langchain/types.js +2 -7
- package/dist/langchain/types.js.map +1 -1
- package/dist/layers/implementations/L0-request-format.d.ts.map +1 -1
- package/dist/layers/implementations/L0-request-format.js +2 -0
- package/dist/layers/implementations/L0-request-format.js.map +1 -1
- package/dist/layers/implementations/L1-input-size.d.ts.map +1 -1
- package/dist/layers/implementations/L1-input-size.js +2 -0
- package/dist/layers/implementations/L1-input-size.js.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.d.ts.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.js +2 -0
- package/dist/layers/implementations/L2-charset-sanitizer.js.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.d.ts.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.js +2 -0
- package/dist/layers/implementations/L3-schema-conformance.js.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.d.ts.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.js +2 -0
- package/dist/layers/implementations/L4-injection-detector.js.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.d.ts.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.js +2 -0
- package/dist/layers/implementations/L5-rate-limiter.js.map +1 -1
- package/dist/layers/implementations/index.d.ts.map +1 -1
- package/dist/layers/implementations/index.js +2 -0
- package/dist/layers/implementations/index.js.map +1 -1
- package/dist/layers/index.d.ts +0 -8
- package/dist/layers/index.d.ts.map +1 -1
- package/dist/layers/index.js +2 -0
- package/dist/layers/index.js.map +1 -1
- package/dist/layers/types.d.ts.map +1 -1
- package/dist/layers/types.js +2 -8
- package/dist/layers/types.js.map +1 -1
- package/dist/paramesphere/activation-collector.d.ts +128 -0
- package/dist/paramesphere/activation-collector.d.ts.map +1 -0
- package/dist/paramesphere/activation-collector.js +260 -0
- package/dist/paramesphere/activation-collector.js.map +1 -0
- package/dist/paramesphere/cognitive-envelope.d.ts +73 -0
- package/dist/paramesphere/cognitive-envelope.d.ts.map +1 -0
- package/dist/paramesphere/cognitive-envelope.js +209 -0
- package/dist/paramesphere/cognitive-envelope.js.map +1 -0
- package/dist/paramesphere/envelope-integration.d.ts +60 -0
- package/dist/paramesphere/envelope-integration.d.ts.map +1 -0
- package/dist/paramesphere/envelope-integration.js +93 -0
- package/dist/paramesphere/envelope-integration.js.map +1 -0
- package/dist/paramesphere/fingerprint-monitor.d.ts +136 -0
- package/dist/paramesphere/fingerprint-monitor.d.ts.map +1 -0
- package/dist/paramesphere/fingerprint-monitor.js +212 -0
- package/dist/paramesphere/fingerprint-monitor.js.map +1 -0
- package/dist/paramesphere/fingerprint-store.d.ts +85 -0
- package/dist/paramesphere/fingerprint-store.d.ts.map +1 -0
- package/dist/paramesphere/fingerprint-store.js +68 -0
- package/dist/paramesphere/fingerprint-store.js.map +1 -0
- package/dist/paramesphere/index.d.ts +21 -0
- package/dist/paramesphere/index.d.ts.map +1 -0
- package/dist/paramesphere/index.js +18 -0
- package/dist/paramesphere/index.js.map +1 -0
- package/dist/paramesphere/monitor-integration.d.ts +37 -0
- package/dist/paramesphere/monitor-integration.d.ts.map +1 -0
- package/dist/paramesphere/monitor-integration.js +81 -0
- package/dist/paramesphere/monitor-integration.js.map +1 -0
- package/dist/paramesphere/paramesphere-engine.d.ts +111 -0
- package/dist/paramesphere/paramesphere-engine.d.ts.map +1 -0
- package/dist/paramesphere/paramesphere-engine.js +542 -0
- package/dist/paramesphere/paramesphere-engine.js.map +1 -0
- package/dist/paramesphere/types.d.ts +142 -0
- package/dist/paramesphere/types.d.ts.map +1 -0
- package/dist/paramesphere/types.js +4 -0
- package/dist/paramesphere/types.js.map +1 -0
- package/dist/persistence/file.d.ts +0 -7
- package/dist/persistence/file.d.ts.map +1 -1
- package/dist/persistence/file.js +2 -0
- package/dist/persistence/file.js.map +1 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +2 -0
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/memory.d.ts.map +1 -1
- package/dist/persistence/memory.js +2 -7
- package/dist/persistence/memory.js.map +1 -1
- package/dist/persistence/sqlite.d.ts +0 -8
- package/dist/persistence/sqlite.d.ts.map +1 -1
- package/dist/persistence/sqlite.js +3 -1
- package/dist/persistence/sqlite.js.map +1 -1
- package/dist/persistence/supabase.d.ts.map +1 -1
- package/dist/persistence/supabase.js +3 -8
- package/dist/persistence/supabase.js.map +1 -1
- package/dist/persistence/types.d.ts.map +1 -1
- package/dist/persistence/types.js +2 -7
- package/dist/persistence/types.js.map +1 -1
- package/dist/phase6/ceiling.d.ts +0 -16
- package/dist/phase6/ceiling.d.ts.map +1 -1
- package/dist/phase6/ceiling.js +2 -0
- package/dist/phase6/ceiling.js.map +1 -1
- package/dist/phase6/context.d.ts +0 -17
- package/dist/phase6/context.d.ts.map +1 -1
- package/dist/phase6/context.js +2 -0
- package/dist/phase6/context.js.map +1 -1
- package/dist/phase6/index.d.ts.map +1 -1
- package/dist/phase6/index.js +2 -0
- package/dist/phase6/index.js.map +1 -1
- package/dist/phase6/presets.d.ts +0 -16
- package/dist/phase6/presets.d.ts.map +1 -1
- package/dist/phase6/presets.js +5 -3
- package/dist/phase6/presets.js.map +1 -1
- package/dist/phase6/provenance.d.ts +0 -15
- package/dist/phase6/provenance.d.ts.map +1 -1
- package/dist/phase6/provenance.js +2 -0
- package/dist/phase6/provenance.js.map +1 -1
- package/dist/phase6/role-gates/index.d.ts.map +1 -1
- package/dist/phase6/role-gates/index.js +2 -0
- package/dist/phase6/role-gates/index.js.map +1 -1
- package/dist/phase6/role-gates/kernel.d.ts.map +1 -1
- package/dist/phase6/role-gates/kernel.js +2 -0
- package/dist/phase6/role-gates/kernel.js.map +1 -1
- package/dist/phase6/role-gates/policy.d.ts.map +1 -1
- package/dist/phase6/role-gates/policy.js +2 -11
- package/dist/phase6/role-gates/policy.js.map +1 -1
- package/dist/phase6/role-gates.d.ts +0 -16
- package/dist/phase6/role-gates.d.ts.map +1 -1
- package/dist/phase6/role-gates.js +2 -0
- package/dist/phase6/role-gates.js.map +1 -1
- package/dist/phase6/types.d.ts +45 -16
- package/dist/phase6/types.d.ts.map +1 -1
- package/dist/phase6/types.js +49 -0
- 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 +2 -0
- package/dist/phase6/weight-presets/canonical.js.map +1 -1
- package/dist/phase6/weight-presets/deltas.d.ts.map +1 -1
- package/dist/phase6/weight-presets/deltas.js +2 -10
- package/dist/phase6/weight-presets/deltas.js.map +1 -1
- package/dist/phase6/weight-presets/index.d.ts.map +1 -1
- package/dist/phase6/weight-presets/index.js +2 -0
- package/dist/phase6/weight-presets/index.js.map +1 -1
- package/dist/phase6/weight-presets/merger.d.ts +0 -10
- package/dist/phase6/weight-presets/merger.d.ts.map +1 -1
- package/dist/phase6/weight-presets/merger.js +2 -0
- package/dist/phase6/weight-presets/merger.js.map +1 -1
- package/dist/proof/index.d.ts +3 -10
- package/dist/proof/index.d.ts.map +1 -1
- package/dist/proof/index.js +27 -9
- package/dist/proof/index.js.map +1 -1
- package/dist/proof/merkle.d.ts +0 -16
- package/dist/proof/merkle.d.ts.map +1 -1
- package/dist/proof/merkle.js +2 -0
- package/dist/proof/merkle.js.map +1 -1
- package/dist/proof/zk-proofs.d.ts +0 -18
- package/dist/proof/zk-proofs.d.ts.map +1 -1
- package/dist/proof/zk-proofs.js +2 -0
- package/dist/proof/zk-proofs.js.map +1 -1
- package/dist/provenance/index.d.ts +0 -8
- package/dist/provenance/index.d.ts.map +1 -1
- package/dist/provenance/index.js +2 -0
- package/dist/provenance/index.js.map +1 -1
- package/dist/provenance/types.d.ts.map +1 -1
- package/dist/provenance/types.js +2 -8
- package/dist/provenance/types.js.map +1 -1
- package/dist/sandbox-training/challenges.d.ts.map +1 -1
- package/dist/sandbox-training/challenges.js +2 -8
- package/dist/sandbox-training/challenges.js.map +1 -1
- package/dist/sandbox-training/graduation.d.ts.map +1 -1
- package/dist/sandbox-training/graduation.js +2 -8
- package/dist/sandbox-training/graduation.js.map +1 -1
- package/dist/sandbox-training/index.d.ts.map +1 -1
- package/dist/sandbox-training/index.js +2 -0
- package/dist/sandbox-training/index.js.map +1 -1
- package/dist/sandbox-training/promotion-service.d.ts.map +1 -1
- package/dist/sandbox-training/promotion-service.js +2 -11
- package/dist/sandbox-training/promotion-service.js.map +1 -1
- package/dist/sandbox-training/runner.d.ts.map +1 -1
- package/dist/sandbox-training/runner.js +2 -8
- package/dist/sandbox-training/runner.js.map +1 -1
- package/dist/sandbox-training/scorer.d.ts.map +1 -1
- package/dist/sandbox-training/scorer.js +2 -8
- package/dist/sandbox-training/scorer.js.map +1 -1
- package/dist/sandbox-training/types.d.ts.map +1 -1
- package/dist/sandbox-training/types.js +2 -8
- package/dist/sandbox-training/types.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts +0 -8
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.js +2 -8
- package/dist/trust-engine/ceiling-enforcement/audit.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/index.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/index.js +2 -0
- package/dist/trust-engine/ceiling-enforcement/index.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/kernel.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/kernel.js +2 -0
- package/dist/trust-engine/ceiling-enforcement/kernel.js.map +1 -1
- package/dist/trust-engine/context-policy/enforcement.d.ts +0 -9
- package/dist/trust-engine/context-policy/enforcement.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/enforcement.js +2 -9
- package/dist/trust-engine/context-policy/enforcement.js.map +1 -1
- package/dist/trust-engine/context-policy/factory.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/factory.js +2 -0
- package/dist/trust-engine/context-policy/factory.js.map +1 -1
- package/dist/trust-engine/context-policy/index.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/index.js +2 -0
- package/dist/trust-engine/context-policy/index.js.map +1 -1
- package/dist/trust-engine/creation-modifiers/index.d.ts.map +1 -1
- package/dist/trust-engine/creation-modifiers/index.js +2 -0
- package/dist/trust-engine/creation-modifiers/index.js.map +1 -1
- package/dist/trust-engine/creation-modifiers/types.d.ts.map +1 -1
- package/dist/trust-engine/creation-modifiers/types.js +2 -0
- package/dist/trust-engine/creation-modifiers/types.js.map +1 -1
- package/dist/trust-engine/decay-profiles.d.ts.map +1 -1
- package/dist/trust-engine/decay-profiles.js +2 -14
- package/dist/trust-engine/decay-profiles.js.map +1 -1
- package/dist/trust-engine/index.d.ts +418 -80
- package/dist/trust-engine/index.d.ts.map +1 -1
- package/dist/trust-engine/index.js +1048 -186
- package/dist/trust-engine/index.js.map +1 -1
- package/dist/trust-engine/phase6-types.d.ts +3 -13
- package/dist/trust-engine/phase6-types.d.ts.map +1 -1
- package/dist/trust-engine/phase6-types.js +5 -13
- package/dist/trust-engine/phase6-types.js.map +1 -1
- package/dist/trust-engine/trust-verifier.d.ts +121 -0
- package/dist/trust-engine/trust-verifier.d.ts.map +1 -0
- package/dist/trust-engine/trust-verifier.js +226 -0
- package/dist/trust-engine/trust-verifier.js.map +1 -0
- package/package.json +140 -135
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Vorion LLC
|
|
1
3
|
/**
|
|
2
4
|
* Trust Engine - Behavioral Trust Scoring
|
|
3
5
|
*
|
|
4
6
|
* Calculates and maintains trust scores for entities based on behavioral signals.
|
|
5
|
-
* Features
|
|
7
|
+
* Features 6-tier trust system with event emission for observability.
|
|
6
8
|
*
|
|
7
9
|
* @packageDocumentation
|
|
8
10
|
*/
|
|
9
11
|
import { EventEmitter } from 'events';
|
|
10
12
|
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
13
|
const logger = createLogger({ component: 'trust-engine' });
|
|
14
14
|
/**
|
|
15
15
|
* Trust level thresholds (8 tiers T0-T7) - per BASIS specification
|
|
@@ -37,13 +37,7 @@ export const TRUST_LEVEL_NAMES = {
|
|
|
37
37
|
6: 'Certified',
|
|
38
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 };
|
|
45
40
|
/**
|
|
46
|
-
* @deprecated Use FACTOR_WEIGHTS for 16-factor scoring. Kept for backwards compatibility.
|
|
47
41
|
* Signal weights for score calculation
|
|
48
42
|
*/
|
|
49
43
|
export const SIGNAL_WEIGHTS = {
|
|
@@ -52,12 +46,91 @@ export const SIGNAL_WEIGHTS = {
|
|
|
52
46
|
identity: 0.2,
|
|
53
47
|
context: 0.15,
|
|
54
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Penalty ratio per BASIS specification: P(T) = 3 + T
|
|
51
|
+
*
|
|
52
|
+
* Negative signals have their EWA weight multiplied by (3 + currentTier),
|
|
53
|
+
* so higher-tier agents are punished MORE severely for failures.
|
|
54
|
+
* T0 = 3×, T1 = 4×, T2 = 5×, T3 = 6×, T4 = 7×, T5 = 8×, T6 = 9×, T7 = 10×
|
|
55
|
+
*
|
|
56
|
+
* Only applies to negative signals (value < neutral 0.5). Positive signals
|
|
57
|
+
* are NOT amplified.
|
|
58
|
+
*/
|
|
59
|
+
export const PENALTY_RATIO_BASE = 3;
|
|
60
|
+
/**
|
|
61
|
+
* Calculate the penalty weight multiplier for a given tier.
|
|
62
|
+
* P(T) = 3 + T
|
|
63
|
+
*/
|
|
64
|
+
export function penaltyMultiplier(tier) {
|
|
65
|
+
return PENALTY_RATIO_BASE + tier;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 16-factor trust model — granular decomposition of the 4-dimension model.
|
|
69
|
+
* Each factor maps to one of the 4 TrustComponents dimensions.
|
|
70
|
+
*/
|
|
71
|
+
export const FACTOR_CODES = [
|
|
72
|
+
'CT-COMP', 'CT-REL', 'CT-OBS', 'CT-TRANS', 'CT-ACCT', 'CT-SAFE',
|
|
73
|
+
'CT-SEC', 'CT-PRIV', 'CT-ID',
|
|
74
|
+
'OP-HUMAN', 'OP-ALIGN', 'OP-CONTEXT',
|
|
75
|
+
'OP-STEW', 'SF-HUM',
|
|
76
|
+
'SF-ADAPT', 'SF-LEARN',
|
|
77
|
+
];
|
|
78
|
+
export const FACTOR_WEIGHTS = {
|
|
79
|
+
'CT-COMP': 0.08, 'CT-REL': 0.07, 'CT-OBS': 0.07, 'CT-TRANS': 0.06,
|
|
80
|
+
'CT-ACCT': 0.06, 'CT-SAFE': 0.05, 'CT-SEC': 0.05, 'CT-PRIV': 0.04, 'CT-ID': 0.04,
|
|
81
|
+
'OP-HUMAN': 0.08, 'OP-ALIGN': 0.08, 'OP-CONTEXT': 0.07, 'OP-STEW': 0.07,
|
|
82
|
+
'SF-HUM': 0.07, 'SF-ADAPT': 0.06, 'SF-LEARN': 0.05,
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Maps each 16-factor code to its parent TrustComponents dimension,
|
|
86
|
+
* enabling signal routing from factor-coded signal types (e.g. CT-COMP.perfect)
|
|
87
|
+
* to the correct component bucket for score calculation.
|
|
88
|
+
*/
|
|
89
|
+
const FACTOR_TO_COMPONENT = {
|
|
90
|
+
'CT-COMP': 'compliance', 'CT-REL': 'compliance', 'CT-OBS': 'compliance',
|
|
91
|
+
'CT-TRANS': 'compliance', 'CT-ACCT': 'compliance',
|
|
92
|
+
'CT-SAFE': 'identity', 'CT-SEC': 'identity', 'CT-PRIV': 'identity', 'CT-ID': 'identity',
|
|
93
|
+
'OP-HUMAN': 'behavioral', 'OP-ALIGN': 'behavioral',
|
|
94
|
+
'OP-CONTEXT': 'context', 'OP-STEW': 'context',
|
|
95
|
+
'SF-HUM': 'behavioral', 'SF-ADAPT': 'behavioral', 'SF-LEARN': 'behavioral',
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Canonical reason codes for time-bound readiness exceptions.
|
|
99
|
+
*/
|
|
100
|
+
export const READINESS_EXCEPTION_REASON_CODES = [
|
|
101
|
+
'approved_leave',
|
|
102
|
+
'planned_maintenance',
|
|
103
|
+
'telemetry_outage',
|
|
104
|
+
'legal_hold',
|
|
105
|
+
'incident_response',
|
|
106
|
+
'dependency_outage',
|
|
107
|
+
];
|
|
108
|
+
const DEFAULT_READINESS_CHECKPOINT_DAYS = [7, 14, 28, 42, 56, 84, 112, 140, 182];
|
|
109
|
+
const DEFAULT_READINESS_REDUCTIONS = [0.06, 0.06, 0.06, 0.06, 0.06, 0.05, 0.05, 0.05, 0.05];
|
|
110
|
+
const DEFAULT_READINESS_EXCEPTION_MAX_DURATION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
111
|
+
function toMs(days) {
|
|
112
|
+
return days * 24 * 60 * 60 * 1000;
|
|
113
|
+
}
|
|
114
|
+
function clampReductionScale(value) {
|
|
115
|
+
if (value === undefined || Number.isNaN(value))
|
|
116
|
+
return 1;
|
|
117
|
+
if (value < 0)
|
|
118
|
+
return 0;
|
|
119
|
+
if (value > 1)
|
|
120
|
+
return 1;
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
55
123
|
/**
|
|
56
124
|
* Trust Engine service with event emission and subscription limits
|
|
57
125
|
*/
|
|
58
126
|
export class TrustEngine extends EventEmitter {
|
|
59
127
|
records = new Map();
|
|
60
|
-
|
|
128
|
+
_decayRate;
|
|
129
|
+
_decayIntervalMs;
|
|
130
|
+
_failureThreshold;
|
|
131
|
+
_acceleratedDecayMultiplier;
|
|
132
|
+
_failureWindowMs;
|
|
133
|
+
_minFailuresForAcceleration;
|
|
61
134
|
_persistence;
|
|
62
135
|
_autoPersist;
|
|
63
136
|
// Recovery configuration
|
|
@@ -73,9 +146,41 @@ export class TrustEngine extends EventEmitter {
|
|
|
73
146
|
_listenerWarningThreshold;
|
|
74
147
|
_listenerCounts = new Map();
|
|
75
148
|
_totalListeners = 0;
|
|
149
|
+
_readinessMode;
|
|
150
|
+
_readinessCheckpointDays;
|
|
151
|
+
_readinessCheckpointReductions;
|
|
152
|
+
_allowedReadinessExceptionReasons;
|
|
153
|
+
_readinessExceptionMaxDurationMs;
|
|
154
|
+
// Signal deduplication state
|
|
155
|
+
_signalDedupEnabled;
|
|
156
|
+
_signalDedupDecayFactor;
|
|
157
|
+
_signalDedupWindowSize;
|
|
158
|
+
_signalDedupWindowMs;
|
|
159
|
+
_signalDedupMinFactor;
|
|
160
|
+
/**
|
|
161
|
+
* Per-entity dedup tracker. Each entry holds recent signal type records
|
|
162
|
+
* used to compute diminishing returns for repeated identical signal types.
|
|
163
|
+
*/
|
|
164
|
+
_dedupState = new Map();
|
|
165
|
+
// Incremental scoring cache — avoids O(n) recalculation on every signal.
|
|
166
|
+
// Cache is engine-side (not serialized into TrustRecord) and is rebuilt on load or every N signals.
|
|
167
|
+
_scoreCache = new Map();
|
|
168
|
+
/** Number of signals between forced full recalculations for drift correction */
|
|
169
|
+
static INCREMENTAL_RECALC_INTERVAL = 100;
|
|
170
|
+
// ParameSphere integration
|
|
171
|
+
_paramesphere;
|
|
172
|
+
_cognitiveEnvelope;
|
|
173
|
+
_fingerprintBaselines = new Map();
|
|
174
|
+
// Trust verifier (optional cryptographic state commitment)
|
|
175
|
+
_verifier;
|
|
76
176
|
constructor(config = {}) {
|
|
77
177
|
super();
|
|
78
|
-
this.
|
|
178
|
+
this._decayRate = config.decayRate ?? 0.01;
|
|
179
|
+
this._decayIntervalMs = config.decayCheckIntervalMs ?? config.decayIntervalMs ?? 60000;
|
|
180
|
+
this._failureThreshold = config.failureThreshold ?? 0.3;
|
|
181
|
+
this._acceleratedDecayMultiplier = config.acceleratedDecayMultiplier ?? 1.0;
|
|
182
|
+
this._failureWindowMs = config.failureWindowMs ?? 3600000; // 1 hour
|
|
183
|
+
this._minFailuresForAcceleration = config.minFailuresForAcceleration ?? 2;
|
|
79
184
|
this._persistence = config.persistence;
|
|
80
185
|
this._autoPersist = config.autoPersist ?? (config.persistence !== undefined);
|
|
81
186
|
// Recovery configuration
|
|
@@ -89,9 +194,209 @@ export class TrustEngine extends EventEmitter {
|
|
|
89
194
|
this._maxListenersPerEvent = config.maxListenersPerEvent ?? 100;
|
|
90
195
|
this._maxTotalListeners = config.maxTotalListeners ?? 1000;
|
|
91
196
|
this._listenerWarningThreshold = config.listenerWarningThreshold ?? 0.8;
|
|
197
|
+
this._readinessMode = config.readinessMode ?? config.freshnessMode ?? (config.decayIntervalMs !== undefined || config.decayRate !== undefined
|
|
198
|
+
? 'legacy_interval'
|
|
199
|
+
: 'checkpoint_schedule');
|
|
200
|
+
this._readinessCheckpointDays =
|
|
201
|
+
config.readinessCheckpointDays ?? config.freshnessCheckpointDays ?? [...DEFAULT_READINESS_CHECKPOINT_DAYS];
|
|
202
|
+
this._readinessCheckpointReductions =
|
|
203
|
+
config.readinessCheckpointReductions ?? config.freshnessCheckpointReductions ?? [...DEFAULT_READINESS_REDUCTIONS];
|
|
204
|
+
if (this._readinessCheckpointDays.length !== this._readinessCheckpointReductions.length) {
|
|
205
|
+
throw new Error('freshnessCheckpointDays and freshnessCheckpointReductions must have equal length');
|
|
206
|
+
}
|
|
207
|
+
this._allowedReadinessExceptionReasons = new Set(config.readinessExceptionAllowedReasons ??
|
|
208
|
+
config.freshnessExceptionAllowedReasons ??
|
|
209
|
+
[...READINESS_EXCEPTION_REASON_CODES]);
|
|
210
|
+
this._readinessExceptionMaxDurationMs =
|
|
211
|
+
config.readinessExceptionMaxDurationMs ??
|
|
212
|
+
config.freshnessExceptionMaxDurationMs ??
|
|
213
|
+
DEFAULT_READINESS_EXCEPTION_MAX_DURATION_MS;
|
|
214
|
+
// Signal deduplication configuration
|
|
215
|
+
this._signalDedupEnabled = config.signalDedupEnabled ?? true;
|
|
216
|
+
this._signalDedupDecayFactor = config.signalDedupDecayFactor ?? 0.5;
|
|
217
|
+
this._signalDedupWindowSize = config.signalDedupWindowSize ?? 10;
|
|
218
|
+
this._signalDedupWindowMs = config.signalDedupWindowMs ?? 3600000; // 1 hour
|
|
219
|
+
this._signalDedupMinFactor = config.signalDedupMinFactor ?? 0.0625; // 1/16
|
|
220
|
+
// ParameSphere integration
|
|
221
|
+
this._paramesphere = config.paramesphere;
|
|
222
|
+
this._cognitiveEnvelope = config.cognitiveEnvelope;
|
|
223
|
+
this._verifier = config.verifier;
|
|
92
224
|
// Set default max listeners on EventEmitter
|
|
93
225
|
this.setMaxListeners(this._maxListenersPerEvent);
|
|
94
226
|
}
|
|
227
|
+
/** Decay check interval in milliseconds */
|
|
228
|
+
get decayCheckIntervalMs() {
|
|
229
|
+
return this._decayIntervalMs;
|
|
230
|
+
}
|
|
231
|
+
validateReadinessExceptionOptions(options) {
|
|
232
|
+
if (!this._allowedReadinessExceptionReasons.has(options.reason)) {
|
|
233
|
+
throw new Error(`Unsupported readiness exception reason: ${options.reason}. ` +
|
|
234
|
+
`Allowed reasons: ${Array.from(this._allowedReadinessExceptionReasons).join(', ')}`);
|
|
235
|
+
}
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
const expiresAtMs = new Date(options.expiresAt).getTime();
|
|
238
|
+
if (Number.isNaN(expiresAtMs)) {
|
|
239
|
+
throw new Error('Invalid expiresAt timestamp for readiness exception');
|
|
240
|
+
}
|
|
241
|
+
if (expiresAtMs <= now) {
|
|
242
|
+
throw new Error('Readiness exception expiresAt must be in the future');
|
|
243
|
+
}
|
|
244
|
+
if (expiresAtMs - now > this._readinessExceptionMaxDurationMs) {
|
|
245
|
+
throw new Error(`Readiness exception duration exceeds configured maximum (${this._readinessExceptionMaxDurationMs} ms)`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
getCheckpointIntervalMs(index) {
|
|
249
|
+
if (index <= 0) {
|
|
250
|
+
return toMs(this._readinessCheckpointDays[0] ?? 7);
|
|
251
|
+
}
|
|
252
|
+
const current = this._readinessCheckpointDays[index] ?? this._readinessCheckpointDays[this._readinessCheckpointDays.length - 1] ?? 7;
|
|
253
|
+
const previous = this._readinessCheckpointDays[index - 1] ?? 0;
|
|
254
|
+
return toMs(Math.max(1, current - previous));
|
|
255
|
+
}
|
|
256
|
+
ensureReadinessState(record) {
|
|
257
|
+
record.readinessCheckpointIndex ??= record.freshnessCheckpointIndex ?? 0;
|
|
258
|
+
record.deferredReadinessMultiplier ??= record.deferredFreshnessMultiplier ?? 1;
|
|
259
|
+
record.readinessBaselineScore ??= record.freshnessBaselineScore ?? record.score;
|
|
260
|
+
record.freshnessCheckpointIndex = record.readinessCheckpointIndex;
|
|
261
|
+
record.deferredFreshnessMultiplier = record.deferredReadinessMultiplier;
|
|
262
|
+
record.freshnessBaselineScore = record.readinessBaselineScore;
|
|
263
|
+
record.freshnessException = record.readinessException ?? record.freshnessException;
|
|
264
|
+
}
|
|
265
|
+
isUsingDefaultReadinessSchedule() {
|
|
266
|
+
if (this._readinessCheckpointDays.length !== DEFAULT_READINESS_CHECKPOINT_DAYS.length) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (this._readinessCheckpointReductions.length !== DEFAULT_READINESS_REDUCTIONS.length) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return this._readinessCheckpointDays.every((v, i) => v === DEFAULT_READINESS_CHECKPOINT_DAYS[i]) &&
|
|
273
|
+
this._readinessCheckpointReductions.every((v, i) => v === DEFAULT_READINESS_REDUCTIONS[i]);
|
|
274
|
+
}
|
|
275
|
+
isReadinessExceptionActive(record, now) {
|
|
276
|
+
const exception = record.readinessException ?? record.freshnessException;
|
|
277
|
+
if (!exception)
|
|
278
|
+
return false;
|
|
279
|
+
const issuedAt = new Date(exception.issuedAt).getTime();
|
|
280
|
+
const expiresAt = new Date(exception.expiresAt).getTime();
|
|
281
|
+
return now >= issuedAt && now < expiresAt;
|
|
282
|
+
}
|
|
283
|
+
async applyDeferredReadinessCatchupIfExpired(record) {
|
|
284
|
+
const exception = record.readinessException ?? record.freshnessException;
|
|
285
|
+
if (!exception)
|
|
286
|
+
return;
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
const expiresAt = new Date(exception.expiresAt).getTime();
|
|
289
|
+
if (now < expiresAt)
|
|
290
|
+
return;
|
|
291
|
+
this.ensureReadinessState(record);
|
|
292
|
+
const deferredMultiplier = record.deferredReadinessMultiplier ?? 1;
|
|
293
|
+
if (deferredMultiplier < 1) {
|
|
294
|
+
const previousScore = record.score;
|
|
295
|
+
const adjustedScore = Math.max(0, Math.round(record.score * deferredMultiplier));
|
|
296
|
+
record.score = adjustedScore;
|
|
297
|
+
record.level = this.scoreToLevel(adjustedScore);
|
|
298
|
+
record.deferredReadinessMultiplier = 1;
|
|
299
|
+
record.deferredFreshnessMultiplier = 1;
|
|
300
|
+
this.emitReadinessAdjustmentEvents(record.entityId, {
|
|
301
|
+
previousScore,
|
|
302
|
+
newScore: record.score,
|
|
303
|
+
stalenessMs: 0,
|
|
304
|
+
accelerated: false,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
delete record.readinessException;
|
|
308
|
+
delete record.freshnessException;
|
|
309
|
+
}
|
|
310
|
+
async applyScheduledReadinessAdjustment(record) {
|
|
311
|
+
this.ensureReadinessState(record);
|
|
312
|
+
await this.applyDeferredReadinessCatchupIfExpired(record);
|
|
313
|
+
while ((record.readinessCheckpointIndex ?? 0) < this._readinessCheckpointDays.length) {
|
|
314
|
+
const checkpointIndex = record.readinessCheckpointIndex ?? 0;
|
|
315
|
+
const intervalMs = this.getCheckpointIntervalMs(checkpointIndex);
|
|
316
|
+
const stalenessMs = Date.now() - new Date(record.lastCalculatedAt).getTime();
|
|
317
|
+
if (stalenessMs < intervalMs) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
const previousScore = record.score;
|
|
321
|
+
const previousLevel = record.level;
|
|
322
|
+
const fullReduction = this._readinessCheckpointReductions[checkpointIndex] ?? 0;
|
|
323
|
+
const activeException = this.isReadinessExceptionActive(record, Date.now());
|
|
324
|
+
const scale = activeException ? clampReductionScale((record.readinessException ?? record.freshnessException)?.reductionScale) : 1;
|
|
325
|
+
const appliedReduction = fullReduction * scale;
|
|
326
|
+
const appliedMultiplier = 1 - appliedReduction;
|
|
327
|
+
const fullMultiplier = 1 - fullReduction;
|
|
328
|
+
record.score = Math.max(0, Math.round(record.score * appliedMultiplier));
|
|
329
|
+
const isFinalCheckpoint = checkpointIndex === this._readinessCheckpointDays.length - 1;
|
|
330
|
+
if (isFinalCheckpoint && !activeException && this.isUsingDefaultReadinessSchedule()) {
|
|
331
|
+
const baseline = record.readinessBaselineScore ?? record.freshnessBaselineScore ?? record.score;
|
|
332
|
+
record.score = Math.max(0, Math.round(baseline * 0.5));
|
|
333
|
+
}
|
|
334
|
+
record.level = this.scoreToLevel(record.score);
|
|
335
|
+
record.readinessCheckpointIndex = checkpointIndex + 1;
|
|
336
|
+
record.freshnessCheckpointIndex = record.readinessCheckpointIndex;
|
|
337
|
+
const lastCalculatedMs = new Date(record.lastCalculatedAt).getTime();
|
|
338
|
+
record.lastCalculatedAt = new Date(lastCalculatedMs + intervalMs).toISOString();
|
|
339
|
+
if (activeException && appliedMultiplier > 0 && fullMultiplier >= 0) {
|
|
340
|
+
const debtFactor = fullMultiplier / appliedMultiplier;
|
|
341
|
+
record.deferredReadinessMultiplier = (record.deferredReadinessMultiplier ?? 1) * debtFactor;
|
|
342
|
+
record.deferredFreshnessMultiplier = record.deferredReadinessMultiplier;
|
|
343
|
+
}
|
|
344
|
+
if (previousScore !== record.score) {
|
|
345
|
+
this.emitReadinessAdjustmentEvents(record.entityId, {
|
|
346
|
+
previousScore,
|
|
347
|
+
newScore: record.score,
|
|
348
|
+
stalenessMs,
|
|
349
|
+
accelerated: false,
|
|
350
|
+
});
|
|
351
|
+
if (previousLevel !== record.level) {
|
|
352
|
+
this.emitTrustEvent({
|
|
353
|
+
type: 'trust:tier_changed',
|
|
354
|
+
entityId: record.entityId,
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
previousLevel,
|
|
357
|
+
newLevel: record.level,
|
|
358
|
+
previousLevelName: TRUST_LEVEL_NAMES[previousLevel],
|
|
359
|
+
newLevelName: TRUST_LEVEL_NAMES[record.level],
|
|
360
|
+
direction: record.level < previousLevel ? 'demoted' : 'promoted',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
await this.autoPersistRecord(record);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
emitReadinessAdjustmentEvents(entityId, details) {
|
|
368
|
+
const adjustmentAmount = details.previousScore - details.newScore;
|
|
369
|
+
this.emitTrustEvent({
|
|
370
|
+
type: 'trust:readiness_adjusted',
|
|
371
|
+
entityId,
|
|
372
|
+
timestamp: new Date().toISOString(),
|
|
373
|
+
previousScore: details.previousScore,
|
|
374
|
+
newScore: details.newScore,
|
|
375
|
+
adjustmentAmount,
|
|
376
|
+
stalenessMs: details.stalenessMs,
|
|
377
|
+
accelerated: details.accelerated,
|
|
378
|
+
});
|
|
379
|
+
this.emitTrustEvent({
|
|
380
|
+
type: 'trust:freshness_adjusted',
|
|
381
|
+
entityId,
|
|
382
|
+
timestamp: new Date().toISOString(),
|
|
383
|
+
previousScore: details.previousScore,
|
|
384
|
+
newScore: details.newScore,
|
|
385
|
+
adjustmentAmount,
|
|
386
|
+
stalenessMs: details.stalenessMs,
|
|
387
|
+
accelerated: details.accelerated,
|
|
388
|
+
});
|
|
389
|
+
this.emitTrustEvent({
|
|
390
|
+
type: 'trust:decay_applied',
|
|
391
|
+
entityId,
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
previousScore: details.previousScore,
|
|
394
|
+
newScore: details.newScore,
|
|
395
|
+
decayAmount: adjustmentAmount,
|
|
396
|
+
stalenessMs: details.stalenessMs,
|
|
397
|
+
accelerated: details.accelerated,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
95
400
|
/**
|
|
96
401
|
* Add event listener with subscription limits
|
|
97
402
|
* @throws Error if listener limits are exceeded
|
|
@@ -198,10 +503,28 @@ export class TrustEngine extends EventEmitter {
|
|
|
198
503
|
};
|
|
199
504
|
}
|
|
200
505
|
/**
|
|
201
|
-
* Get the decay
|
|
506
|
+
* Get the current decay rate
|
|
202
507
|
*/
|
|
203
|
-
get
|
|
204
|
-
return this.
|
|
508
|
+
get decayRate() {
|
|
509
|
+
return this._decayRate;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Get the decay interval in milliseconds
|
|
513
|
+
*/
|
|
514
|
+
get decayIntervalMs() {
|
|
515
|
+
return this._decayIntervalMs;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get the failure threshold
|
|
519
|
+
*/
|
|
520
|
+
get failureThreshold() {
|
|
521
|
+
return this._failureThreshold;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get the accelerated decay multiplier
|
|
525
|
+
*/
|
|
526
|
+
get acceleratedDecayMultiplier() {
|
|
527
|
+
return this._acceleratedDecayMultiplier;
|
|
205
528
|
}
|
|
206
529
|
/**
|
|
207
530
|
* Get the persistence provider
|
|
@@ -236,6 +559,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
236
559
|
}
|
|
237
560
|
const records = await this._persistence.query();
|
|
238
561
|
this.records.clear();
|
|
562
|
+
this._scoreCache.clear(); // Invalidate incremental caches on reload
|
|
239
563
|
for (const record of records) {
|
|
240
564
|
this.records.set(record.entityId, record);
|
|
241
565
|
}
|
|
@@ -265,6 +589,122 @@ export class TrustEngine extends EventEmitter {
|
|
|
265
589
|
await this._persistence.save(record);
|
|
266
590
|
}
|
|
267
591
|
}
|
|
592
|
+
// -----------------------------------------------------------------------
|
|
593
|
+
// ParameSphere Integration
|
|
594
|
+
// -----------------------------------------------------------------------
|
|
595
|
+
/**
|
|
596
|
+
* Check an entity's model fingerprint for drift.
|
|
597
|
+
* If drift exceeds threshold, automatically records a negative trust signal.
|
|
598
|
+
* Returns the drift result.
|
|
599
|
+
*/
|
|
600
|
+
async checkFingerprint(entityId, weights, activations) {
|
|
601
|
+
if (!this._paramesphere) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const current = this._paramesphere.computeFingerprint(weights, activations);
|
|
605
|
+
const baseline = this._fingerprintBaselines.get(entityId);
|
|
606
|
+
if (!baseline) {
|
|
607
|
+
// First call — store as baseline
|
|
608
|
+
this._fingerprintBaselines.set(entityId, current);
|
|
609
|
+
this.emitTrustEvent({
|
|
610
|
+
type: 'trust:fingerprint_baseline_set',
|
|
611
|
+
entityId,
|
|
612
|
+
timestamp: new Date().toISOString(),
|
|
613
|
+
fingerprintSha256: current.sha256,
|
|
614
|
+
});
|
|
615
|
+
logger.info({ entityId, sha256: current.sha256 }, 'Fingerprint baseline set');
|
|
616
|
+
return { drifted: false, similarity: 1.0, magnitude: 0 };
|
|
617
|
+
}
|
|
618
|
+
// Compare against baseline
|
|
619
|
+
const comparison = this._paramesphere.compareFingerprints(baseline, current);
|
|
620
|
+
const similarity = comparison.cosineSimilarity;
|
|
621
|
+
const magnitude = comparison.l2Distance;
|
|
622
|
+
const drifted = comparison.driftDetected;
|
|
623
|
+
if (drifted) {
|
|
624
|
+
// Auto-record a negative signal — maximum negative triggers full asymmetric penalty
|
|
625
|
+
await this.recordSignal({
|
|
626
|
+
id: `paramesphere-drift-${entityId}-${Date.now()}`,
|
|
627
|
+
entityId,
|
|
628
|
+
type: 'paramesphere:drift',
|
|
629
|
+
value: 0.0,
|
|
630
|
+
source: 'system:paramesphere',
|
|
631
|
+
timestamp: new Date().toISOString(),
|
|
632
|
+
metadata: {
|
|
633
|
+
similarity,
|
|
634
|
+
magnitude,
|
|
635
|
+
driftThreshold: comparison.cosineDistance,
|
|
636
|
+
cosineDistance: comparison.cosineDistance,
|
|
637
|
+
maxSingularValueDelta: comparison.maxSingularValueDelta,
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
this.emitTrustEvent({
|
|
641
|
+
type: 'trust:fingerprint_drift',
|
|
642
|
+
entityId,
|
|
643
|
+
timestamp: new Date().toISOString(),
|
|
644
|
+
similarity,
|
|
645
|
+
magnitude,
|
|
646
|
+
driftThreshold: comparison.cosineDistance,
|
|
647
|
+
});
|
|
648
|
+
logger.warn({ entityId, similarity, magnitude, cosineDistance: comparison.cosineDistance }, 'Fingerprint drift detected — negative trust signal recorded');
|
|
649
|
+
}
|
|
650
|
+
return { drifted, similarity, magnitude };
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Set or update the fingerprint baseline for an entity.
|
|
654
|
+
* Call after initial model load or after a verified safe update.
|
|
655
|
+
*/
|
|
656
|
+
async setFingerprintBaseline(entityId, weights, activations) {
|
|
657
|
+
if (!this._paramesphere) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const fingerprint = this._paramesphere.computeFingerprint(weights, activations);
|
|
661
|
+
this._fingerprintBaselines.set(entityId, fingerprint);
|
|
662
|
+
this.emitTrustEvent({
|
|
663
|
+
type: 'trust:fingerprint_baseline_set',
|
|
664
|
+
entityId,
|
|
665
|
+
timestamp: new Date().toISOString(),
|
|
666
|
+
fingerprintSha256: fingerprint.sha256,
|
|
667
|
+
});
|
|
668
|
+
logger.info({ entityId, sha256: fingerprint.sha256 }, 'Fingerprint baseline set');
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Check if entity behavioral signals are within cognitive envelope bounds.
|
|
672
|
+
* If breach detected, records a negative trust signal with multiplier.
|
|
673
|
+
*/
|
|
674
|
+
async checkCognitiveEnvelope(entityId, observation) {
|
|
675
|
+
if (!this._cognitiveEnvelope) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
const result = this._cognitiveEnvelope.checkBreach(observation);
|
|
679
|
+
const breached = result.breached;
|
|
680
|
+
if (breached) {
|
|
681
|
+
// Auto-record a negative signal for envelope breach
|
|
682
|
+
await this.recordSignal({
|
|
683
|
+
id: `paramesphere-envelope-${entityId}-${Date.now()}`,
|
|
684
|
+
entityId,
|
|
685
|
+
type: 'paramesphere:envelope_breach',
|
|
686
|
+
value: 0.0,
|
|
687
|
+
source: 'system:paramesphere',
|
|
688
|
+
timestamp: new Date().toISOString(),
|
|
689
|
+
metadata: {
|
|
690
|
+
maxDeviation: result.maxDeviation,
|
|
691
|
+
trustMultiplier: result.trustMultiplier,
|
|
692
|
+
breachCounter: result.breachCounter,
|
|
693
|
+
dimensionBreaches: result.dimensionBreaches,
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
this.emitTrustEvent({
|
|
697
|
+
type: 'trust:envelope_breach',
|
|
698
|
+
entityId,
|
|
699
|
+
timestamp: new Date().toISOString(),
|
|
700
|
+
distance: result.maxDeviation,
|
|
701
|
+
trustMultiplier: result.trustMultiplier,
|
|
702
|
+
breachCounter: result.breachCounter,
|
|
703
|
+
});
|
|
704
|
+
logger.warn({ entityId, maxDeviation: result.maxDeviation, breachCounter: result.breachCounter }, 'Cognitive envelope breach detected — negative trust signal recorded');
|
|
705
|
+
}
|
|
706
|
+
return { breached, distance: result.maxDeviation };
|
|
707
|
+
}
|
|
268
708
|
/**
|
|
269
709
|
* Close the trust engine and persistence provider
|
|
270
710
|
*/
|
|
@@ -272,6 +712,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
272
712
|
if (this._persistence) {
|
|
273
713
|
await this._persistence.close();
|
|
274
714
|
}
|
|
715
|
+
this._scoreCache.clear();
|
|
275
716
|
this.removeAllListeners();
|
|
276
717
|
}
|
|
277
718
|
/**
|
|
@@ -288,34 +729,41 @@ export class TrustEngine extends EventEmitter {
|
|
|
288
729
|
async calculate(entityId) {
|
|
289
730
|
const record = this.records.get(entityId);
|
|
290
731
|
const signals = record?.signals ?? [];
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
}
|
|
732
|
+
const currentLevel = record?.level ?? 1;
|
|
733
|
+
// Calculate component scores
|
|
734
|
+
const components = this.calculateComponents(signals, currentLevel);
|
|
735
|
+
// Calculate weighted total
|
|
736
|
+
const score = Math.round(components.behavioral * SIGNAL_WEIGHTS.behavioral * 1000 +
|
|
737
|
+
components.compliance * SIGNAL_WEIGHTS.compliance * 1000 +
|
|
738
|
+
components.identity * SIGNAL_WEIGHTS.identity * 1000 +
|
|
739
|
+
components.context * SIGNAL_WEIGHTS.context * 1000);
|
|
304
740
|
// Clamp to valid range
|
|
305
741
|
const clampedScore = Math.max(0, Math.min(1000, score));
|
|
306
742
|
const level = this.scoreToLevel(clampedScore);
|
|
307
|
-
// Backwards compat: also compute legacy 4-bucket components
|
|
308
|
-
const components = this.calculateComponents(signals);
|
|
309
743
|
const factors = this.getSignificantFactors(components);
|
|
310
|
-
logger.debug({ entityId, score: clampedScore, level,
|
|
744
|
+
logger.debug({ entityId, score: clampedScore, level, components }, 'Trust calculated');
|
|
311
745
|
return {
|
|
312
746
|
score: clampedScore,
|
|
313
747
|
level,
|
|
314
748
|
components,
|
|
315
|
-
factorScores,
|
|
316
749
|
factors,
|
|
317
750
|
};
|
|
318
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* Check if an entity has accelerated decay active
|
|
754
|
+
*/
|
|
755
|
+
hasAcceleratedDecay(record) {
|
|
756
|
+
const now = Date.now();
|
|
757
|
+
const recentFailures = record.recentFailures.filter((timestamp) => now - new Date(timestamp).getTime() < this._failureWindowMs);
|
|
758
|
+
return recentFailures.length >= this._minFailuresForAcceleration;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Clean up old failure timestamps outside the window
|
|
762
|
+
*/
|
|
763
|
+
cleanupFailures(record) {
|
|
764
|
+
const now = Date.now();
|
|
765
|
+
record.recentFailures = record.recentFailures.filter((timestamp) => now - new Date(timestamp).getTime() < this._failureWindowMs);
|
|
766
|
+
}
|
|
319
767
|
/**
|
|
320
768
|
* Clean up old success timestamps outside the window
|
|
321
769
|
*/
|
|
@@ -420,14 +868,25 @@ export class TrustEngine extends EventEmitter {
|
|
|
420
868
|
async getScore(entityId) {
|
|
421
869
|
const record = this.records.get(entityId);
|
|
422
870
|
if (record) {
|
|
423
|
-
//
|
|
871
|
+
// Clean up old failures
|
|
872
|
+
this.cleanupFailures(record);
|
|
873
|
+
if (this._readinessMode === 'checkpoint_schedule') {
|
|
874
|
+
await this.applyScheduledReadinessAdjustment(record);
|
|
875
|
+
return record;
|
|
876
|
+
}
|
|
877
|
+
// Apply decay if stale
|
|
424
878
|
const staleness = Date.now() - new Date(record.lastCalculatedAt).getTime();
|
|
425
|
-
if (staleness > this.
|
|
879
|
+
if (staleness > this._decayIntervalMs) {
|
|
426
880
|
const previousScore = record.score;
|
|
427
881
|
const previousLevel = record.level;
|
|
428
|
-
//
|
|
429
|
-
const
|
|
430
|
-
const
|
|
882
|
+
// Check if accelerated decay should apply
|
|
883
|
+
const accelerated = this.hasAcceleratedDecay(record);
|
|
884
|
+
const effectiveDecayRate = accelerated
|
|
885
|
+
? this._decayRate * this._acceleratedDecayMultiplier
|
|
886
|
+
: this._decayRate;
|
|
887
|
+
// Apply decay based on staleness
|
|
888
|
+
const decayPeriods = Math.floor(staleness / this._decayIntervalMs);
|
|
889
|
+
const decayMultiplier = Math.pow(1 - effectiveDecayRate, decayPeriods);
|
|
431
890
|
const decayedScore = Math.round(record.score * decayMultiplier);
|
|
432
891
|
const clampedScore = Math.max(0, decayedScore);
|
|
433
892
|
record.score = clampedScore;
|
|
@@ -443,6 +902,7 @@ export class TrustEngine extends EventEmitter {
|
|
|
443
902
|
newScore: record.score,
|
|
444
903
|
decayAmount: previousScore - record.score,
|
|
445
904
|
stalenessMs: staleness,
|
|
905
|
+
accelerated,
|
|
446
906
|
});
|
|
447
907
|
// Emit tier change if applicable
|
|
448
908
|
if (previousLevel !== record.level) {
|
|
@@ -465,34 +925,25 @@ export class TrustEngine extends EventEmitter {
|
|
|
465
925
|
return record;
|
|
466
926
|
}
|
|
467
927
|
/**
|
|
468
|
-
*
|
|
469
|
-
* Rejects NaN, Infinity, out-of-range values, and missing required fields.
|
|
470
|
-
*
|
|
471
|
-
* @throws Error on invalid signal
|
|
928
|
+
* Record a trust signal
|
|
472
929
|
*/
|
|
473
|
-
|
|
474
|
-
|
|
930
|
+
async recordSignal(signal) {
|
|
931
|
+
// Input validation
|
|
932
|
+
if (signal == null) {
|
|
475
933
|
throw new Error('Signal is required');
|
|
476
934
|
}
|
|
477
935
|
if (!signal.entityId) {
|
|
478
|
-
throw new Error('
|
|
936
|
+
throw new Error('entityId is required');
|
|
479
937
|
}
|
|
480
|
-
if (!signal.type
|
|
481
|
-
throw new Error('
|
|
938
|
+
if (!signal.type) {
|
|
939
|
+
throw new Error('type is required');
|
|
482
940
|
}
|
|
483
|
-
if (!Number.isFinite(signal.value)) {
|
|
484
|
-
throw new Error(
|
|
941
|
+
if (typeof signal.value !== 'number' || !Number.isFinite(signal.value)) {
|
|
942
|
+
throw new Error('Invalid signal value');
|
|
485
943
|
}
|
|
486
944
|
if (signal.value < 0 || signal.value > 1) {
|
|
487
|
-
throw new Error(
|
|
945
|
+
throw new Error('Signal value out of range');
|
|
488
946
|
}
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Record a trust signal
|
|
492
|
-
* @throws Error if signal is invalid (NaN, out of range, missing fields)
|
|
493
|
-
*/
|
|
494
|
-
async recordSignal(signal) {
|
|
495
|
-
this.validateSignal(signal);
|
|
496
947
|
let record = this.records.get(signal.entityId);
|
|
497
948
|
let isNewEntity = false;
|
|
498
949
|
if (!record) {
|
|
@@ -500,12 +951,53 @@ export class TrustEngine extends EventEmitter {
|
|
|
500
951
|
this.records.set(signal.entityId, record);
|
|
501
952
|
isNewEntity = true;
|
|
502
953
|
}
|
|
954
|
+
// Apply signal deduplication (diminishing returns for repeated signal types)
|
|
955
|
+
const dedupResult = this.applySignalDedup(signal);
|
|
956
|
+
if (dedupResult.adjusted) {
|
|
957
|
+
signal = { ...signal, value: dedupResult.value };
|
|
958
|
+
this.emitTrustEvent({
|
|
959
|
+
type: 'trust:signal_deduplicated',
|
|
960
|
+
entityId: signal.entityId,
|
|
961
|
+
timestamp: new Date().toISOString(),
|
|
962
|
+
signal,
|
|
963
|
+
originalValue: dedupResult.originalValue,
|
|
964
|
+
adjustedValue: dedupResult.value,
|
|
965
|
+
diminishingFactor: dedupResult.factor,
|
|
966
|
+
repeatCount: dedupResult.repeatCount,
|
|
967
|
+
});
|
|
968
|
+
logger.info({
|
|
969
|
+
entityId: signal.entityId,
|
|
970
|
+
signalType: signal.type,
|
|
971
|
+
originalValue: dedupResult.originalValue,
|
|
972
|
+
adjustedValue: dedupResult.value,
|
|
973
|
+
diminishingFactor: dedupResult.factor,
|
|
974
|
+
repeatCount: dedupResult.repeatCount,
|
|
975
|
+
}, 'Signal deduplication applied — diminishing returns for repeated signal type');
|
|
976
|
+
}
|
|
503
977
|
const previousScore = record.score;
|
|
504
978
|
const previousLevel = record.level;
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
979
|
+
// Detect failure signals
|
|
980
|
+
if (signal.value < this._failureThreshold) {
|
|
981
|
+
record.recentFailures.push(signal.timestamp);
|
|
982
|
+
this.cleanupFailures(record);
|
|
983
|
+
// Reset consecutive successes on failure
|
|
508
984
|
record.consecutiveSuccesses = 0;
|
|
985
|
+
const acceleratedDecayActive = this.hasAcceleratedDecay(record);
|
|
986
|
+
this.emitTrustEvent({
|
|
987
|
+
type: 'trust:failure_detected',
|
|
988
|
+
entityId: signal.entityId,
|
|
989
|
+
timestamp: new Date().toISOString(),
|
|
990
|
+
signal,
|
|
991
|
+
failureCount: record.recentFailures.length,
|
|
992
|
+
acceleratedDecayActive,
|
|
993
|
+
});
|
|
994
|
+
logger.warn({
|
|
995
|
+
entityId: signal.entityId,
|
|
996
|
+
signalType: signal.type,
|
|
997
|
+
signalValue: signal.value,
|
|
998
|
+
failureCount: record.recentFailures.length,
|
|
999
|
+
acceleratedDecayActive,
|
|
1000
|
+
}, 'Failure signal detected');
|
|
509
1001
|
}
|
|
510
1002
|
// Detect success signals and apply recovery
|
|
511
1003
|
if (signal.value >= this._successThreshold) {
|
|
@@ -528,17 +1020,38 @@ export class TrustEngine extends EventEmitter {
|
|
|
528
1020
|
// Add signal
|
|
529
1021
|
record.signals.push(signal);
|
|
530
1022
|
// Keep only recent signals (last 1000)
|
|
1023
|
+
// If we sliced, invalidate the cache since the signal array changed
|
|
531
1024
|
if (record.signals.length > 1000) {
|
|
532
1025
|
record.signals = record.signals.slice(-1000);
|
|
1026
|
+
this._scoreCache.delete(signal.entityId);
|
|
1027
|
+
}
|
|
1028
|
+
// Recalculate — use incremental O(1) path when cache is fresh,
|
|
1029
|
+
// otherwise fall back to full O(n) recalculation and rebuild the cache.
|
|
1030
|
+
let calculation;
|
|
1031
|
+
const existingCache = this._scoreCache.get(signal.entityId);
|
|
1032
|
+
const signalsSinceFullRecalc = existingCache
|
|
1033
|
+
? record.signals.length - existingCache.lastFullRecalcAt
|
|
1034
|
+
: Infinity;
|
|
1035
|
+
if (existingCache && signalsSinceFullRecalc < TrustEngine.INCREMENTAL_RECALC_INTERVAL) {
|
|
1036
|
+
// O(1) incremental update
|
|
1037
|
+
calculation = this.incrementalUpdate(signal, record.level);
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
// Full O(n) recalculation + cache rebuild
|
|
1041
|
+
calculation = await this.calculate(signal.entityId);
|
|
1042
|
+
this.buildCacheFromSignals(record.signals, record.level, signal.entityId);
|
|
533
1043
|
}
|
|
534
|
-
// Recalculate
|
|
535
|
-
const calculation = await this.calculate(signal.entityId);
|
|
536
1044
|
// Update record
|
|
537
1045
|
record.score = calculation.score;
|
|
538
1046
|
record.level = calculation.level;
|
|
539
1047
|
record.components = calculation.components;
|
|
540
|
-
record.factorScores = calculation.factorScores;
|
|
541
1048
|
record.lastCalculatedAt = new Date().toISOString();
|
|
1049
|
+
record.readinessCheckpointIndex = 0;
|
|
1050
|
+
record.deferredReadinessMultiplier = 1;
|
|
1051
|
+
record.readinessBaselineScore = calculation.score;
|
|
1052
|
+
record.freshnessCheckpointIndex = 0;
|
|
1053
|
+
record.deferredFreshnessMultiplier = 1;
|
|
1054
|
+
record.freshnessBaselineScore = calculation.score;
|
|
542
1055
|
// Record history if significant change
|
|
543
1056
|
if (Math.abs(calculation.score - previousScore) >= 10) {
|
|
544
1057
|
record.history.push({
|
|
@@ -586,6 +1099,8 @@ export class TrustEngine extends EventEmitter {
|
|
|
586
1099
|
direction: calculation.level > previousLevel ? 'promoted' : 'demoted',
|
|
587
1100
|
});
|
|
588
1101
|
}
|
|
1102
|
+
// Commit state to verifier if configured
|
|
1103
|
+
this._verifier?.commitState(signal.entityId, calculation.score, calculation.level, record.signals.length);
|
|
589
1104
|
// Auto-persist if enabled
|
|
590
1105
|
await this.autoPersistRecord(record);
|
|
591
1106
|
logger.debug({
|
|
@@ -599,11 +1114,6 @@ export class TrustEngine extends EventEmitter {
|
|
|
599
1114
|
*/
|
|
600
1115
|
async initializeEntity(entityId, initialLevel = 1) {
|
|
601
1116
|
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
|
-
}
|
|
607
1117
|
const record = {
|
|
608
1118
|
entityId,
|
|
609
1119
|
score,
|
|
@@ -614,7 +1124,6 @@ export class TrustEngine extends EventEmitter {
|
|
|
614
1124
|
identity: 0.5,
|
|
615
1125
|
context: 0.5,
|
|
616
1126
|
},
|
|
617
|
-
factorScores: initialFactorScores,
|
|
618
1127
|
signals: [],
|
|
619
1128
|
lastCalculatedAt: new Date().toISOString(),
|
|
620
1129
|
history: [
|
|
@@ -625,11 +1134,20 @@ export class TrustEngine extends EventEmitter {
|
|
|
625
1134
|
timestamp: new Date().toISOString(),
|
|
626
1135
|
},
|
|
627
1136
|
],
|
|
1137
|
+
recentFailures: [],
|
|
628
1138
|
recentSuccesses: [],
|
|
629
1139
|
peakScore: score,
|
|
630
1140
|
consecutiveSuccesses: 0,
|
|
1141
|
+
readinessCheckpointIndex: 0,
|
|
1142
|
+
deferredReadinessMultiplier: 1,
|
|
1143
|
+
readinessBaselineScore: score,
|
|
1144
|
+
freshnessCheckpointIndex: 0,
|
|
1145
|
+
deferredFreshnessMultiplier: 1,
|
|
1146
|
+
freshnessBaselineScore: score,
|
|
631
1147
|
};
|
|
632
1148
|
this.records.set(entityId, record);
|
|
1149
|
+
// Commit initial state to verifier if configured
|
|
1150
|
+
this._verifier?.commitState(entityId, score, initialLevel, 0);
|
|
633
1151
|
// Auto-persist if enabled
|
|
634
1152
|
await this.autoPersistRecord(record);
|
|
635
1153
|
// Emit initialized event
|
|
@@ -644,87 +1162,70 @@ export class TrustEngine extends EventEmitter {
|
|
|
644
1162
|
return record;
|
|
645
1163
|
}
|
|
646
1164
|
/**
|
|
647
|
-
*
|
|
648
|
-
*/
|
|
649
|
-
getEntityIds() {
|
|
650
|
-
return Array.from(this.records.keys());
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Get trust level name
|
|
654
|
-
*/
|
|
655
|
-
getLevelName(level) {
|
|
656
|
-
return TRUST_LEVEL_NAMES[level];
|
|
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
|
-
* ```
|
|
1165
|
+
* Explain a trust score with full factor breakdown
|
|
671
1166
|
*/
|
|
672
1167
|
async explainScore(entityId) {
|
|
673
1168
|
const record = this.records.get(entityId);
|
|
674
1169
|
if (!record) {
|
|
675
|
-
throw new Error(
|
|
1170
|
+
throw new Error('Entity not found');
|
|
676
1171
|
}
|
|
677
|
-
const
|
|
678
|
-
const
|
|
679
|
-
|
|
1172
|
+
const level = record.level;
|
|
1173
|
+
const levelName = TRUST_LEVEL_NAMES[level] ?? 'Unknown';
|
|
1174
|
+
const levelRange = TRUST_THRESHOLDS[level];
|
|
1175
|
+
// Build factor breakdown from recorded signals
|
|
680
1176
|
const factorBreakdown = FACTOR_CODES.map((code) => {
|
|
681
|
-
const weight = FACTOR_WEIGHTS[code];
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
|
|
1177
|
+
const weight = FACTOR_WEIGHTS[code] ?? 0;
|
|
1178
|
+
// Calculate raw score for this factor from matching signals
|
|
1179
|
+
const matchingSignals = record.signals.filter((s) => s.type.startsWith(code + '.') || s.type === code);
|
|
1180
|
+
const rawScore = matchingSignals.length > 0
|
|
1181
|
+
? matchingSignals.reduce((sum, s) => sum + s.value, 0) / matchingSignals.length
|
|
1182
|
+
: 0;
|
|
1183
|
+
return {
|
|
1184
|
+
code,
|
|
1185
|
+
weight,
|
|
1186
|
+
rawScore,
|
|
1187
|
+
contribution: rawScore * weight * 1000,
|
|
1188
|
+
};
|
|
685
1189
|
});
|
|
686
|
-
//
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
.
|
|
692
|
-
|
|
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;
|
|
1190
|
+
// Days since last signal
|
|
1191
|
+
let daysSinceLastSignal = null;
|
|
1192
|
+
if (record.signals.length > 0) {
|
|
1193
|
+
const lastSignal = record.signals[record.signals.length - 1];
|
|
1194
|
+
const lastTimestamp = new Date(lastSignal.timestamp).getTime();
|
|
1195
|
+
daysSinceLastSignal = (Date.now() - lastTimestamp) / (1000 * 60 * 60 * 24);
|
|
1196
|
+
}
|
|
708
1197
|
// Points to next level
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
1198
|
+
let pointsToNextLevel = null;
|
|
1199
|
+
const nextLevel = (level + 1);
|
|
1200
|
+
if (TRUST_THRESHOLDS[nextLevel]) {
|
|
1201
|
+
pointsToNextLevel = TRUST_THRESHOLDS[nextLevel].min - record.score;
|
|
1202
|
+
}
|
|
712
1203
|
return {
|
|
713
1204
|
entityId,
|
|
714
1205
|
score: record.score,
|
|
715
|
-
level
|
|
716
|
-
levelName
|
|
717
|
-
levelRange
|
|
718
|
-
|
|
719
|
-
signalCount: signals.length,
|
|
1206
|
+
level,
|
|
1207
|
+
levelName,
|
|
1208
|
+
levelRange,
|
|
1209
|
+
signalCount: record.signals.length,
|
|
720
1210
|
factorBreakdown,
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
daysSinceLastSignal: daysSinceLastSignal !== null ? Math.round(daysSinceLastSignal * 100) / 100 : null,
|
|
1211
|
+
daysSinceLastSignal,
|
|
1212
|
+
decayMultiplier: 1.0,
|
|
1213
|
+
pointsToNextLevel,
|
|
725
1214
|
generatedAt: new Date().toISOString(),
|
|
726
1215
|
};
|
|
727
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Get all entity IDs
|
|
1219
|
+
*/
|
|
1220
|
+
getEntityIds() {
|
|
1221
|
+
return Array.from(this.records.keys());
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Get trust level name
|
|
1225
|
+
*/
|
|
1226
|
+
getLevelName(level) {
|
|
1227
|
+
return TRUST_LEVEL_NAMES[level];
|
|
1228
|
+
}
|
|
728
1229
|
/**
|
|
729
1230
|
* Convert score to trust level
|
|
730
1231
|
*/
|
|
@@ -737,61 +1238,70 @@ export class TrustEngine extends EventEmitter {
|
|
|
737
1238
|
return 0;
|
|
738
1239
|
}
|
|
739
1240
|
/**
|
|
740
|
-
* @deprecated Use calculateFactorScores for 16-factor model. Kept for backwards compatibility.
|
|
741
1241
|
* Calculate component scores from signals
|
|
742
1242
|
*/
|
|
743
|
-
calculateComponents(signals) {
|
|
744
|
-
// Group signals by
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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);
|
|
1243
|
+
calculateComponents(signals, currentLevel) {
|
|
1244
|
+
// Group signals by component — supports both legacy 4-prefix types
|
|
1245
|
+
// (e.g. "behavioral.task_completion") and 16-factor code types
|
|
1246
|
+
// (e.g. "CT-COMP.perfect") via FACTOR_TO_COMPONENT mapping.
|
|
1247
|
+
const behavioral = [];
|
|
1248
|
+
const compliance = [];
|
|
1249
|
+
const identity = [];
|
|
1250
|
+
const context = [];
|
|
1251
|
+
for (const s of signals) {
|
|
1252
|
+
// Legacy 4-prefix routing
|
|
1253
|
+
if (s.type.startsWith('behavioral.')) {
|
|
1254
|
+
behavioral.push(s);
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (s.type.startsWith('compliance.')) {
|
|
1258
|
+
compliance.push(s);
|
|
773
1259
|
continue;
|
|
774
1260
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1261
|
+
if (s.type.startsWith('identity.')) {
|
|
1262
|
+
identity.push(s);
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (s.type.startsWith('context.')) {
|
|
1266
|
+
context.push(s);
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
// 16-factor code routing: extract prefix before the first '.'
|
|
1270
|
+
const dotIndex = s.type.indexOf('.');
|
|
1271
|
+
const prefix = dotIndex > 0 ? s.type.substring(0, dotIndex) : s.type;
|
|
1272
|
+
const component = FACTOR_TO_COMPONENT[prefix];
|
|
1273
|
+
if (component) {
|
|
1274
|
+
switch (component) {
|
|
1275
|
+
case 'behavioral':
|
|
1276
|
+
behavioral.push(s);
|
|
1277
|
+
break;
|
|
1278
|
+
case 'compliance':
|
|
1279
|
+
compliance.push(s);
|
|
1280
|
+
break;
|
|
1281
|
+
case 'identity':
|
|
1282
|
+
identity.push(s);
|
|
1283
|
+
break;
|
|
1284
|
+
case 'context':
|
|
1285
|
+
context.push(s);
|
|
1286
|
+
break;
|
|
781
1287
|
}
|
|
782
1288
|
}
|
|
783
1289
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1290
|
+
return {
|
|
1291
|
+
behavioral: this.averageSignalValue(behavioral, 0.5, currentLevel),
|
|
1292
|
+
compliance: this.averageSignalValue(compliance, 0.5, currentLevel),
|
|
1293
|
+
identity: this.averageSignalValue(identity, 0.5, currentLevel),
|
|
1294
|
+
context: this.averageSignalValue(context, 0.5, currentLevel),
|
|
1295
|
+
};
|
|
790
1296
|
}
|
|
791
1297
|
/**
|
|
792
|
-
* Calculate average signal value with default
|
|
1298
|
+
* Calculate average signal value with default.
|
|
1299
|
+
*
|
|
1300
|
+
* Applies the BASIS penalty ratio P(T) = 3 + T to negative signals:
|
|
1301
|
+
* negative signal weights are multiplied by (3 + currentTier) so that
|
|
1302
|
+
* higher-tier agents suffer proportionally larger score drops from failures.
|
|
793
1303
|
*/
|
|
794
|
-
averageSignalValue(signals, defaultValue) {
|
|
1304
|
+
averageSignalValue(signals, defaultValue, currentLevel) {
|
|
795
1305
|
if (signals.length === 0)
|
|
796
1306
|
return defaultValue;
|
|
797
1307
|
// Weight recent signals more heavily
|
|
@@ -800,12 +1310,176 @@ export class TrustEngine extends EventEmitter {
|
|
|
800
1310
|
let totalWeight = 0;
|
|
801
1311
|
for (const signal of signals) {
|
|
802
1312
|
const age = now - new Date(signal.timestamp).getTime();
|
|
803
|
-
|
|
804
|
-
|
|
1313
|
+
let weight = Math.exp(-age / (7 * 24 * 60 * 60 * 1000)); // 7-day signal recency weight (distinct from 182-day trust decay)
|
|
1314
|
+
// BASIS penalty ratio: amplify weight of negative signals by P(T) = 3 + T.
|
|
1315
|
+
// A negative signal is one whose value falls below the neutral default (0.5).
|
|
1316
|
+
// This counteracts EWA dilution at higher tiers, ensuring that failures
|
|
1317
|
+
// at T7 (10× weight) hurt far more than failures at T0 (3× weight).
|
|
1318
|
+
if (signal.value < defaultValue) {
|
|
1319
|
+
weight *= penaltyMultiplier(currentLevel);
|
|
1320
|
+
}
|
|
1321
|
+
const adjustedValue = this.adjustSignalValueForTier(signal, defaultValue, currentLevel);
|
|
1322
|
+
weightedSum += adjustedValue * weight;
|
|
805
1323
|
totalWeight += weight;
|
|
806
1324
|
}
|
|
807
1325
|
return totalWeight > 0 ? weightedSum / totalWeight : defaultValue;
|
|
808
1326
|
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Human/manual positive approvals carry less upward impact at higher tiers.
|
|
1329
|
+
*/
|
|
1330
|
+
adjustSignalValueForTier(signal, defaultValue, currentLevel) {
|
|
1331
|
+
if (!this.isHumanApprovalSignal(signal) || signal.value <= defaultValue) {
|
|
1332
|
+
return signal.value;
|
|
1333
|
+
}
|
|
1334
|
+
const assistFactor = this.getHumanApprovalAssistFactor(currentLevel);
|
|
1335
|
+
return defaultValue + (signal.value - defaultValue) * assistFactor;
|
|
1336
|
+
}
|
|
1337
|
+
isHumanApprovalSignal(signal) {
|
|
1338
|
+
const source = signal.source?.toLowerCase() ?? '';
|
|
1339
|
+
return source === 'manual' || source === 'human' || source === 'human_review' || source === 'human-approval';
|
|
1340
|
+
}
|
|
1341
|
+
getHumanApprovalAssistFactor(level) {
|
|
1342
|
+
switch (level) {
|
|
1343
|
+
case 0:
|
|
1344
|
+
case 1:
|
|
1345
|
+
return 1.0;
|
|
1346
|
+
case 2:
|
|
1347
|
+
return 0.85;
|
|
1348
|
+
case 3:
|
|
1349
|
+
return 0.7;
|
|
1350
|
+
case 4:
|
|
1351
|
+
return 0.55;
|
|
1352
|
+
case 5:
|
|
1353
|
+
return 0.4;
|
|
1354
|
+
case 6:
|
|
1355
|
+
return 0.3;
|
|
1356
|
+
case 7:
|
|
1357
|
+
return 0.2;
|
|
1358
|
+
default:
|
|
1359
|
+
return 1.0;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Resolve which component bucket a signal type belongs to.
|
|
1364
|
+
* Returns the component key or undefined if the signal type is unrecognized.
|
|
1365
|
+
*/
|
|
1366
|
+
resolveSignalComponent(signalType) {
|
|
1367
|
+
// Legacy 4-prefix routing
|
|
1368
|
+
if (signalType.startsWith('behavioral.'))
|
|
1369
|
+
return 'behavioral';
|
|
1370
|
+
if (signalType.startsWith('compliance.'))
|
|
1371
|
+
return 'compliance';
|
|
1372
|
+
if (signalType.startsWith('identity.'))
|
|
1373
|
+
return 'identity';
|
|
1374
|
+
if (signalType.startsWith('context.'))
|
|
1375
|
+
return 'context';
|
|
1376
|
+
// 16-factor code routing: extract prefix before the first '.'
|
|
1377
|
+
const dotIndex = signalType.indexOf('.');
|
|
1378
|
+
const prefix = dotIndex > 0 ? signalType.substring(0, dotIndex) : signalType;
|
|
1379
|
+
return Object.hasOwn(FACTOR_TO_COMPONENT, prefix) ? FACTOR_TO_COMPONENT[prefix] : undefined;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Create a fresh empty CachedScoreState.
|
|
1383
|
+
*/
|
|
1384
|
+
createEmptyCacheState() {
|
|
1385
|
+
return {
|
|
1386
|
+
behavioral: { weightedSum: 0, totalWeight: 0, count: 0 },
|
|
1387
|
+
compliance: { weightedSum: 0, totalWeight: 0, count: 0 },
|
|
1388
|
+
identity: { weightedSum: 0, totalWeight: 0, count: 0 },
|
|
1389
|
+
context: { weightedSum: 0, totalWeight: 0, count: 0 },
|
|
1390
|
+
lastFullRecalcAt: 0,
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Build the score cache from a full recalculation of all stored signals.
|
|
1395
|
+
* This mirrors the logic in averageSignalValue but captures the running totals.
|
|
1396
|
+
*/
|
|
1397
|
+
buildCacheFromSignals(signals, currentLevel, entityId) {
|
|
1398
|
+
const cache = this.createEmptyCacheState();
|
|
1399
|
+
cache.lastFullRecalcAt = signals.length;
|
|
1400
|
+
const now = Date.now();
|
|
1401
|
+
const defaultValue = 0.5;
|
|
1402
|
+
for (const signal of signals) {
|
|
1403
|
+
const component = this.resolveSignalComponent(signal.type);
|
|
1404
|
+
if (!component)
|
|
1405
|
+
continue;
|
|
1406
|
+
const bucket = cache[component];
|
|
1407
|
+
const age = now - new Date(signal.timestamp).getTime();
|
|
1408
|
+
let weight = Math.exp(-age / (7 * 24 * 60 * 60 * 1000));
|
|
1409
|
+
// BASIS penalty ratio: amplify weight of negative signals
|
|
1410
|
+
if (signal.value < defaultValue) {
|
|
1411
|
+
weight *= penaltyMultiplier(currentLevel);
|
|
1412
|
+
}
|
|
1413
|
+
const adjustedValue = this.adjustSignalValueForTier(signal, defaultValue, currentLevel);
|
|
1414
|
+
bucket.weightedSum += adjustedValue * weight;
|
|
1415
|
+
bucket.totalWeight += weight;
|
|
1416
|
+
bucket.count++;
|
|
1417
|
+
}
|
|
1418
|
+
this._scoreCache.set(entityId, cache);
|
|
1419
|
+
return cache;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Incrementally update the cached score state with a single new signal.
|
|
1423
|
+
* Returns the updated TrustCalculation. This is O(1) per signal.
|
|
1424
|
+
*/
|
|
1425
|
+
incrementalUpdate(signal, currentLevel) {
|
|
1426
|
+
const cache = this._scoreCache.get(signal.entityId);
|
|
1427
|
+
if (!cache) {
|
|
1428
|
+
// Should never happen — caller must ensure cache exists
|
|
1429
|
+
throw new Error('Incremental update called without cache');
|
|
1430
|
+
}
|
|
1431
|
+
const component = this.resolveSignalComponent(signal.type);
|
|
1432
|
+
const defaultValue = 0.5;
|
|
1433
|
+
if (component) {
|
|
1434
|
+
const bucket = cache[component];
|
|
1435
|
+
const now = Date.now();
|
|
1436
|
+
const age = now - new Date(signal.timestamp).getTime();
|
|
1437
|
+
let weight = Math.exp(-age / (7 * 24 * 60 * 60 * 1000));
|
|
1438
|
+
if (signal.value < defaultValue) {
|
|
1439
|
+
weight *= penaltyMultiplier(currentLevel);
|
|
1440
|
+
}
|
|
1441
|
+
const adjustedValue = this.adjustSignalValueForTier(signal, defaultValue, currentLevel);
|
|
1442
|
+
bucket.weightedSum += adjustedValue * weight;
|
|
1443
|
+
bucket.totalWeight += weight;
|
|
1444
|
+
bucket.count++;
|
|
1445
|
+
}
|
|
1446
|
+
// Derive component averages from cache
|
|
1447
|
+
const components = {
|
|
1448
|
+
behavioral: cache.behavioral.totalWeight > 0
|
|
1449
|
+
? cache.behavioral.weightedSum / cache.behavioral.totalWeight
|
|
1450
|
+
: defaultValue,
|
|
1451
|
+
compliance: cache.compliance.totalWeight > 0
|
|
1452
|
+
? cache.compliance.weightedSum / cache.compliance.totalWeight
|
|
1453
|
+
: defaultValue,
|
|
1454
|
+
identity: cache.identity.totalWeight > 0
|
|
1455
|
+
? cache.identity.weightedSum / cache.identity.totalWeight
|
|
1456
|
+
: defaultValue,
|
|
1457
|
+
context: cache.context.totalWeight > 0
|
|
1458
|
+
? cache.context.weightedSum / cache.context.totalWeight
|
|
1459
|
+
: defaultValue,
|
|
1460
|
+
};
|
|
1461
|
+
// Calculate weighted total (same formula as calculate())
|
|
1462
|
+
const score = Math.round(components.behavioral * SIGNAL_WEIGHTS.behavioral * 1000 +
|
|
1463
|
+
components.compliance * SIGNAL_WEIGHTS.compliance * 1000 +
|
|
1464
|
+
components.identity * SIGNAL_WEIGHTS.identity * 1000 +
|
|
1465
|
+
components.context * SIGNAL_WEIGHTS.context * 1000);
|
|
1466
|
+
const clampedScore = Math.max(0, Math.min(1000, score));
|
|
1467
|
+
const level = this.scoreToLevel(clampedScore);
|
|
1468
|
+
const factors = this.getSignificantFactors(components);
|
|
1469
|
+
return { score: clampedScore, level, components, factors };
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Invalidate the incremental score cache for an entity,
|
|
1473
|
+
* forcing the next recordSignal to do a full recalculation.
|
|
1474
|
+
*/
|
|
1475
|
+
invalidateScoreCache(entityId) {
|
|
1476
|
+
if (entityId) {
|
|
1477
|
+
this._scoreCache.delete(entityId);
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
this._scoreCache.clear();
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
809
1483
|
/**
|
|
810
1484
|
* Get significant factors affecting the score
|
|
811
1485
|
*/
|
|
@@ -825,16 +1499,97 @@ export class TrustEngine extends EventEmitter {
|
|
|
825
1499
|
}
|
|
826
1500
|
return factors;
|
|
827
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Apply signal deduplication with diminishing returns.
|
|
1504
|
+
*
|
|
1505
|
+
* Tracks recent signal types per entity within a sliding window. When the same
|
|
1506
|
+
* signal type appears consecutively (within the window), its effective value is
|
|
1507
|
+
* pulled toward 0.5 (neutral) by an exponentially decaying factor. This prevents
|
|
1508
|
+
* replay attacks from inflating or deflating trust scores while still allowing
|
|
1509
|
+
* legitimate repeated signals to have some (diminished) impact.
|
|
1510
|
+
*
|
|
1511
|
+
* The adjustment formula moves the value toward 0.5 (the neutral baseline):
|
|
1512
|
+
* adjustedValue = 0.5 + (originalValue - 0.5) * factor
|
|
1513
|
+
*
|
|
1514
|
+
* This means:
|
|
1515
|
+
* - Positive signals (value > 0.5) have their positive impact reduced
|
|
1516
|
+
* - Negative signals (value < 0.5) have their negative impact reduced
|
|
1517
|
+
* - A value of exactly 0.5 is never adjusted
|
|
1518
|
+
*/
|
|
1519
|
+
applySignalDedup(signal) {
|
|
1520
|
+
const originalValue = signal.value;
|
|
1521
|
+
if (!this._signalDedupEnabled) {
|
|
1522
|
+
return { adjusted: false, value: originalValue, originalValue, factor: 1, repeatCount: 0 };
|
|
1523
|
+
}
|
|
1524
|
+
const now = Date.now();
|
|
1525
|
+
const signalTs = new Date(signal.timestamp).getTime() || now;
|
|
1526
|
+
// Get or create entity dedup history
|
|
1527
|
+
let history = this._dedupState.get(signal.entityId);
|
|
1528
|
+
if (!history) {
|
|
1529
|
+
history = [];
|
|
1530
|
+
this._dedupState.set(signal.entityId, history);
|
|
1531
|
+
}
|
|
1532
|
+
// Prune entries outside the time window and enforce window size
|
|
1533
|
+
history = history.filter((entry) => now - entry.timestamp < this._signalDedupWindowMs);
|
|
1534
|
+
if (history.length > this._signalDedupWindowSize) {
|
|
1535
|
+
history = history.slice(-this._signalDedupWindowSize);
|
|
1536
|
+
}
|
|
1537
|
+
// Count consecutive occurrences of this signal type (from most recent backwards)
|
|
1538
|
+
let repeatCount = 0;
|
|
1539
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
1540
|
+
if (history[i].type === signal.type) {
|
|
1541
|
+
repeatCount++;
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
break; // Stop counting when a different signal type is encountered
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
// Record this signal in the dedup history
|
|
1548
|
+
history.push({ type: signal.type, timestamp: signalTs });
|
|
1549
|
+
// Enforce window size after push
|
|
1550
|
+
if (history.length > this._signalDedupWindowSize) {
|
|
1551
|
+
history = history.slice(-this._signalDedupWindowSize);
|
|
1552
|
+
}
|
|
1553
|
+
this._dedupState.set(signal.entityId, history);
|
|
1554
|
+
// No adjustment needed for first occurrence
|
|
1555
|
+
if (repeatCount === 0) {
|
|
1556
|
+
return { adjusted: false, value: originalValue, originalValue, factor: 1, repeatCount: 0 };
|
|
1557
|
+
}
|
|
1558
|
+
// Calculate diminishing factor: decayFactor^repeatCount, floored at minFactor
|
|
1559
|
+
const rawFactor = Math.pow(this._signalDedupDecayFactor, repeatCount);
|
|
1560
|
+
const factor = Math.max(rawFactor, this._signalDedupMinFactor);
|
|
1561
|
+
// Pull value toward 0.5 (neutral) by the diminishing factor
|
|
1562
|
+
const adjustedValue = 0.5 + (originalValue - 0.5) * factor;
|
|
1563
|
+
return {
|
|
1564
|
+
adjusted: true,
|
|
1565
|
+
value: Math.max(0, Math.min(1, adjustedValue)),
|
|
1566
|
+
originalValue,
|
|
1567
|
+
factor,
|
|
1568
|
+
repeatCount,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Check whether signal deduplication is enabled
|
|
1573
|
+
*/
|
|
1574
|
+
get signalDedupEnabled() {
|
|
1575
|
+
return this._signalDedupEnabled;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Clear dedup state for an entity (useful for testing or resets)
|
|
1579
|
+
*/
|
|
1580
|
+
clearDedupState(entityId) {
|
|
1581
|
+
if (entityId) {
|
|
1582
|
+
this._dedupState.delete(entityId);
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
this._dedupState.clear();
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
828
1588
|
/**
|
|
829
1589
|
* Create initial trust record
|
|
830
1590
|
*/
|
|
831
1591
|
createInitialRecord(entityId) {
|
|
832
1592
|
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
|
-
}
|
|
838
1593
|
return {
|
|
839
1594
|
entityId,
|
|
840
1595
|
score: initialScore, // Start at L1 (Provisional) minimum
|
|
@@ -845,14 +1600,83 @@ export class TrustEngine extends EventEmitter {
|
|
|
845
1600
|
identity: 0.5,
|
|
846
1601
|
context: 0.5,
|
|
847
1602
|
},
|
|
848
|
-
factorScores: initialFactorScores,
|
|
849
1603
|
signals: [],
|
|
850
1604
|
lastCalculatedAt: new Date().toISOString(),
|
|
851
1605
|
history: [],
|
|
1606
|
+
recentFailures: [],
|
|
852
1607
|
recentSuccesses: [],
|
|
853
1608
|
peakScore: initialScore,
|
|
854
1609
|
consecutiveSuccesses: 0,
|
|
1610
|
+
readinessCheckpointIndex: 0,
|
|
1611
|
+
deferredReadinessMultiplier: 1,
|
|
1612
|
+
readinessBaselineScore: initialScore,
|
|
1613
|
+
freshnessCheckpointIndex: 0,
|
|
1614
|
+
deferredFreshnessMultiplier: 1,
|
|
1615
|
+
freshnessBaselineScore: initialScore,
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Configure a time-bound Readiness Degree exception for an entity.
|
|
1620
|
+
*/
|
|
1621
|
+
setReadinessException(entityId, options) {
|
|
1622
|
+
const record = this.records.get(entityId);
|
|
1623
|
+
if (!record) {
|
|
1624
|
+
throw new Error(`Entity not found: ${entityId}`);
|
|
1625
|
+
}
|
|
1626
|
+
this.validateReadinessExceptionOptions(options);
|
|
1627
|
+
this.ensureReadinessState(record);
|
|
1628
|
+
record.readinessException = {
|
|
1629
|
+
reason: options.reason,
|
|
1630
|
+
issuedAt: new Date().toISOString(),
|
|
1631
|
+
expiresAt: options.expiresAt,
|
|
1632
|
+
reductionScale: clampReductionScale(options.reductionScale),
|
|
855
1633
|
};
|
|
1634
|
+
record.freshnessException = record.readinessException;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* @deprecated Use setReadinessException.
|
|
1638
|
+
*/
|
|
1639
|
+
setFreshnessException(entityId, options) {
|
|
1640
|
+
this.setReadinessException(entityId, options);
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Clear Readiness Degree exception for an entity.
|
|
1644
|
+
*/
|
|
1645
|
+
clearReadinessException(entityId) {
|
|
1646
|
+
const record = this.records.get(entityId);
|
|
1647
|
+
if (!record) {
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
delete record.readinessException;
|
|
1651
|
+
delete record.freshnessException;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* @deprecated Use clearReadinessException.
|
|
1655
|
+
*/
|
|
1656
|
+
clearFreshnessException(entityId) {
|
|
1657
|
+
this.clearReadinessException(entityId);
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Get active Readiness Degree exception for an entity.
|
|
1661
|
+
*/
|
|
1662
|
+
getReadinessException(entityId) {
|
|
1663
|
+
const record = this.records.get(entityId);
|
|
1664
|
+
return record?.readinessException ?? record?.freshnessException;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* @deprecated Use getReadinessException.
|
|
1668
|
+
*/
|
|
1669
|
+
getFreshnessException(entityId) {
|
|
1670
|
+
return this.getReadinessException(entityId);
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Check if accelerated decay is currently active for an entity
|
|
1674
|
+
*/
|
|
1675
|
+
isAcceleratedDecayActive(entityId) {
|
|
1676
|
+
const record = this.records.get(entityId);
|
|
1677
|
+
if (!record)
|
|
1678
|
+
return false;
|
|
1679
|
+
return this.hasAcceleratedDecay(record);
|
|
856
1680
|
}
|
|
857
1681
|
/**
|
|
858
1682
|
* Check if accelerated recovery is currently active for an entity
|
|
@@ -863,6 +1687,16 @@ export class TrustEngine extends EventEmitter {
|
|
|
863
1687
|
return false;
|
|
864
1688
|
return this.hasAcceleratedRecovery(record);
|
|
865
1689
|
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Get current failure count for an entity
|
|
1692
|
+
*/
|
|
1693
|
+
getFailureCount(entityId) {
|
|
1694
|
+
const record = this.records.get(entityId);
|
|
1695
|
+
if (!record)
|
|
1696
|
+
return 0;
|
|
1697
|
+
this.cleanupFailures(record);
|
|
1698
|
+
return record.recentFailures.length;
|
|
1699
|
+
}
|
|
866
1700
|
/**
|
|
867
1701
|
* Get consecutive success count for an entity
|
|
868
1702
|
*/
|
|
@@ -881,6 +1715,34 @@ export class TrustEngine extends EventEmitter {
|
|
|
881
1715
|
return 0;
|
|
882
1716
|
return record.peakScore;
|
|
883
1717
|
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Verify an entity's trust score against its verifier commitment chain.
|
|
1720
|
+
* Requires a TrustVerifier to be configured; throws if none is set.
|
|
1721
|
+
*/
|
|
1722
|
+
verifyEntity(entityId) {
|
|
1723
|
+
if (!this._verifier) {
|
|
1724
|
+
throw new Error('No TrustVerifier configured');
|
|
1725
|
+
}
|
|
1726
|
+
const record = this.records.get(entityId);
|
|
1727
|
+
if (!record) {
|
|
1728
|
+
throw new Error('Entity not found');
|
|
1729
|
+
}
|
|
1730
|
+
return this._verifier.verifyAgent(entityId, record.score, record.level);
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Verify all tracked entities against the verifier commitment chain.
|
|
1734
|
+
* Requires a TrustVerifier to be configured; throws if none is set.
|
|
1735
|
+
*/
|
|
1736
|
+
verifyAll() {
|
|
1737
|
+
if (!this._verifier) {
|
|
1738
|
+
throw new Error('No TrustVerifier configured');
|
|
1739
|
+
}
|
|
1740
|
+
const scores = new Map();
|
|
1741
|
+
for (const [id, record] of this.records) {
|
|
1742
|
+
scores.set(id, { score: record.score, tier: record.level });
|
|
1743
|
+
}
|
|
1744
|
+
return this._verifier.verifyAll(scores);
|
|
1745
|
+
}
|
|
884
1746
|
}
|
|
885
1747
|
/**
|
|
886
1748
|
* Create a new Trust Engine instance
|