@sovr/engine 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1091 -7
- package/dist/index.d.ts +1091 -7
- package/dist/index.js +2286 -0
- package/dist/index.mjs +2272 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,16 +22,30 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AdaptiveThresholdManager: () => AdaptiveThresholdManager,
|
|
24
24
|
AutoHardenEngine: () => AutoHardenEngine,
|
|
25
|
+
ContextAccelerator: () => ContextAccelerator,
|
|
26
|
+
ContributionSettlementEngine: () => ContributionSettlementEngine,
|
|
25
27
|
CostGateEnhancedEngine: () => CostGateEnhancedEngine,
|
|
28
|
+
DEFAULT_CA_CONFIG: () => DEFAULT_CA_CONFIG,
|
|
29
|
+
DEFAULT_GE_CONFIG: () => DEFAULT_GE_CONFIG,
|
|
30
|
+
DEFAULT_HARD_RULES: () => DEFAULT_HARD_RULES,
|
|
26
31
|
DEFAULT_RULES: () => DEFAULT_RULES,
|
|
32
|
+
DEFAULT_SCORING_WEIGHTS: () => DEFAULT_SCORING_WEIGHTS,
|
|
33
|
+
DEFAULT_TSA_CONFIG: () => DEFAULT_TSA_CONFIG,
|
|
27
34
|
EvolutionChannelEngine: () => EvolutionChannelEngine,
|
|
28
35
|
FeatureSwitchesManager: () => FeatureSwitchesManager,
|
|
36
|
+
GovernanceEnhancer: () => GovernanceEnhancer,
|
|
29
37
|
MultiLevelBudgetEngine: () => MultiLevelBudgetEngine,
|
|
38
|
+
OverrideDriftAnalyzer: () => OverrideDriftAnalyzer,
|
|
30
39
|
PolicyEngine: () => PolicyEngine,
|
|
31
40
|
PricingRulesEngine: () => PricingRulesEngine,
|
|
32
41
|
RecalculationEngine: () => RecalculationEngine,
|
|
33
42
|
SOVR_FEATURE_SWITCHES: () => SOVR_FEATURE_SWITCHES,
|
|
43
|
+
SelfConstraintEngine: () => SelfConstraintEngine,
|
|
34
44
|
SemanticDriftDetectorEngine: () => SemanticDriftDetectorEngine,
|
|
45
|
+
TimeSeriesAggregator: () => TimeSeriesAggregator,
|
|
46
|
+
TrendAlertEngine: () => TrendAlertEngine,
|
|
47
|
+
TwoPhaseRouter: () => TwoPhaseRouter,
|
|
48
|
+
ValuationModel: () => ValuationModel,
|
|
35
49
|
compileFromJSON: () => compileFromJSON,
|
|
36
50
|
compileRuleSet: () => compileRuleSet,
|
|
37
51
|
createAutoHardenEngine: () => createAutoHardenEngine,
|
|
@@ -2054,6 +2068,2264 @@ function createEvolutionChannel(config) {
|
|
|
2054
2068
|
return new EvolutionChannelEngine(config);
|
|
2055
2069
|
}
|
|
2056
2070
|
|
|
2071
|
+
// src/twoPhaseRouter.ts
|
|
2072
|
+
var DEFAULT_HARD_RULES = [
|
|
2073
|
+
{
|
|
2074
|
+
id: "HR-001",
|
|
2075
|
+
name: "high_risk_requires_powerful",
|
|
2076
|
+
condition: (req, candidate) => (req.riskLevel === "critical" || req.riskLevel === "high") && candidate.tier === "cheap",
|
|
2077
|
+
reasonCode: "RISK_TIER_MISMATCH",
|
|
2078
|
+
reasonTemplate: "High/critical risk tasks cannot use cheap tier models",
|
|
2079
|
+
enabled: true
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
id: "HR-002",
|
|
2083
|
+
name: "critical_requires_powerful_only",
|
|
2084
|
+
condition: (req, candidate) => req.riskLevel === "critical" && candidate.tier !== "powerful",
|
|
2085
|
+
reasonCode: "RISK_TIER_MISMATCH",
|
|
2086
|
+
reasonTemplate: "Critical risk tasks require powerful tier models only",
|
|
2087
|
+
enabled: true
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
id: "HR-003",
|
|
2091
|
+
name: "budget_hard_cap",
|
|
2092
|
+
condition: (req, candidate) => {
|
|
2093
|
+
if (!req.maxCostUsd) return false;
|
|
2094
|
+
const estimatedCost = req.estimatedTokens / 1e3 * candidate.costPer1kTokens;
|
|
2095
|
+
return estimatedCost > req.maxCostUsd;
|
|
2096
|
+
},
|
|
2097
|
+
reasonCode: "COST_EXCEEDS_BUDGET",
|
|
2098
|
+
reasonTemplate: "Estimated cost exceeds hard budget cap",
|
|
2099
|
+
enabled: true
|
|
2100
|
+
}
|
|
2101
|
+
];
|
|
2102
|
+
var DEFAULT_SCORING_WEIGHTS = {
|
|
2103
|
+
cost: 0.25,
|
|
2104
|
+
latency: 0.2,
|
|
2105
|
+
capability: 0.25,
|
|
2106
|
+
weight: 0.15,
|
|
2107
|
+
tierMatch: 0.15
|
|
2108
|
+
};
|
|
2109
|
+
var TwoPhaseRouter = class {
|
|
2110
|
+
config;
|
|
2111
|
+
constructor(config) {
|
|
2112
|
+
this.config = {
|
|
2113
|
+
...config,
|
|
2114
|
+
hardRules: config.hardRules.length > 0 ? config.hardRules : DEFAULT_HARD_RULES,
|
|
2115
|
+
scoringWeights: config.scoringWeights ?? DEFAULT_SCORING_WEIGHTS
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Phase 1: Eligibility — 硬性规则过滤
|
|
2120
|
+
*/
|
|
2121
|
+
evaluateEligibility(request) {
|
|
2122
|
+
return this.config.models.map((candidate) => {
|
|
2123
|
+
if (!candidate.enabled) {
|
|
2124
|
+
return {
|
|
2125
|
+
candidateId: candidate.id,
|
|
2126
|
+
eligible: false,
|
|
2127
|
+
reasonCode: "DISABLED",
|
|
2128
|
+
reasonDetail: `Model ${candidate.name} is disabled`
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
const missingCaps = request.requiredCapabilities.filter(
|
|
2132
|
+
(cap) => !candidate.capabilities.includes(cap)
|
|
2133
|
+
);
|
|
2134
|
+
if (missingCaps.length > 0) {
|
|
2135
|
+
return {
|
|
2136
|
+
candidateId: candidate.id,
|
|
2137
|
+
eligible: false,
|
|
2138
|
+
reasonCode: "CAPABILITY_MISSING",
|
|
2139
|
+
reasonDetail: `Missing capabilities: ${missingCaps.join(", ")}`
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
if (request.estimatedTokens > candidate.maxContextTokens) {
|
|
2143
|
+
return {
|
|
2144
|
+
candidateId: candidate.id,
|
|
2145
|
+
eligible: false,
|
|
2146
|
+
reasonCode: "CONTEXT_TOO_LARGE",
|
|
2147
|
+
reasonDetail: `Estimated ${request.estimatedTokens} tokens exceeds max ${candidate.maxContextTokens}`
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
if (request.maxLatencyMs && candidate.avgLatencyMs > request.maxLatencyMs) {
|
|
2151
|
+
return {
|
|
2152
|
+
candidateId: candidate.id,
|
|
2153
|
+
eligible: false,
|
|
2154
|
+
reasonCode: "LATENCY_EXCEEDS_LIMIT",
|
|
2155
|
+
reasonDetail: `Avg latency ${candidate.avgLatencyMs}ms exceeds limit ${request.maxLatencyMs}ms`
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
for (const rule of this.config.hardRules) {
|
|
2159
|
+
if (!rule.enabled) continue;
|
|
2160
|
+
if (rule.condition(request, candidate)) {
|
|
2161
|
+
return {
|
|
2162
|
+
candidateId: candidate.id,
|
|
2163
|
+
eligible: false,
|
|
2164
|
+
reasonCode: rule.reasonCode,
|
|
2165
|
+
reasonDetail: rule.reasonTemplate
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
candidateId: candidate.id,
|
|
2171
|
+
eligible: true,
|
|
2172
|
+
reasonCode: "ELIGIBLE",
|
|
2173
|
+
reasonDetail: "Passed all eligibility checks"
|
|
2174
|
+
};
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Phase 2: Scoring — 软性指标加权评分
|
|
2179
|
+
*/
|
|
2180
|
+
evaluateScoring(request, eligibleIds) {
|
|
2181
|
+
const eligible = this.config.models.filter((m) => eligibleIds.has(m.id));
|
|
2182
|
+
if (eligible.length === 0) return [];
|
|
2183
|
+
const w = this.config.scoringWeights;
|
|
2184
|
+
const maxCost = Math.max(...eligible.map((m) => m.costPer1kTokens), 1e-3);
|
|
2185
|
+
const maxLatency = Math.max(...eligible.map((m) => m.avgLatencyMs), 1);
|
|
2186
|
+
return eligible.map((candidate) => {
|
|
2187
|
+
const costScore = 100 * (1 - candidate.costPer1kTokens / maxCost);
|
|
2188
|
+
const latencyScore = 100 * (1 - candidate.avgLatencyMs / maxLatency);
|
|
2189
|
+
const matchedCaps = request.requiredCapabilities.filter(
|
|
2190
|
+
(cap) => candidate.capabilities.includes(cap)
|
|
2191
|
+
).length;
|
|
2192
|
+
const capabilityScore = request.requiredCapabilities.length > 0 ? 100 * (matchedCaps / request.requiredCapabilities.length) : 50;
|
|
2193
|
+
const weightScore = candidate.weight;
|
|
2194
|
+
let tierMatchScore = 50;
|
|
2195
|
+
if (request.preferredTier) {
|
|
2196
|
+
if (candidate.tier === request.preferredTier) tierMatchScore = 100;
|
|
2197
|
+
else {
|
|
2198
|
+
const tierOrder = ["cheap", "balanced", "powerful"];
|
|
2199
|
+
const diff = Math.abs(
|
|
2200
|
+
tierOrder.indexOf(candidate.tier) - tierOrder.indexOf(request.preferredTier)
|
|
2201
|
+
);
|
|
2202
|
+
tierMatchScore = Math.max(0, 100 - diff * 40);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
const score = costScore * w.cost + latencyScore * w.latency + capabilityScore * w.capability + weightScore * w.weight + tierMatchScore * w.tierMatch;
|
|
2206
|
+
return {
|
|
2207
|
+
candidateId: candidate.id,
|
|
2208
|
+
score: Math.round(score * 100) / 100,
|
|
2209
|
+
breakdown: {
|
|
2210
|
+
costScore: Math.round(costScore * 100) / 100,
|
|
2211
|
+
latencyScore: Math.round(latencyScore * 100) / 100,
|
|
2212
|
+
capabilityScore: Math.round(capabilityScore * 100) / 100,
|
|
2213
|
+
weightScore: Math.round(weightScore * 100) / 100,
|
|
2214
|
+
tierMatchScore: Math.round(tierMatchScore * 100) / 100
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Main routing decision
|
|
2221
|
+
*/
|
|
2222
|
+
async route(request) {
|
|
2223
|
+
const startTime = Date.now();
|
|
2224
|
+
const phase1Results = this.evaluateEligibility(request);
|
|
2225
|
+
const eligibleIds = new Set(
|
|
2226
|
+
phase1Results.filter((r) => r.eligible).map((r) => r.candidateId)
|
|
2227
|
+
);
|
|
2228
|
+
const phase2Results = this.evaluateScoring(request, eligibleIds);
|
|
2229
|
+
phase2Results.sort((a, b) => b.score - a.score);
|
|
2230
|
+
let selectedModelId;
|
|
2231
|
+
let fallbackUsed = false;
|
|
2232
|
+
const reasonChain = [];
|
|
2233
|
+
if (phase2Results.length > 0) {
|
|
2234
|
+
selectedModelId = phase2Results[0].candidateId;
|
|
2235
|
+
reasonChain.push(
|
|
2236
|
+
`Phase 1: ${eligibleIds.size}/${this.config.models.length} candidates eligible`
|
|
2237
|
+
);
|
|
2238
|
+
reasonChain.push(
|
|
2239
|
+
`Phase 2: Top scorer = ${selectedModelId} (score: ${phase2Results[0].score})`
|
|
2240
|
+
);
|
|
2241
|
+
const rejected = phase1Results.filter((r) => !r.eligible);
|
|
2242
|
+
if (rejected.length > 0) {
|
|
2243
|
+
reasonChain.push(
|
|
2244
|
+
`Rejected: ${rejected.map((r) => `${r.candidateId}(${r.reasonCode})`).join(", ")}`
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
} else if (this.config.fallbackModelId) {
|
|
2248
|
+
selectedModelId = this.config.fallbackModelId;
|
|
2249
|
+
fallbackUsed = true;
|
|
2250
|
+
reasonChain.push("Phase 1: No eligible candidates");
|
|
2251
|
+
reasonChain.push(`Fallback: Using ${selectedModelId}`);
|
|
2252
|
+
} else {
|
|
2253
|
+
const leastBad = this.config.models.find((m) => m.enabled);
|
|
2254
|
+
selectedModelId = leastBad?.id ?? this.config.models[0]?.id ?? "unknown";
|
|
2255
|
+
fallbackUsed = true;
|
|
2256
|
+
reasonChain.push("Phase 1: No eligible candidates, no fallback configured");
|
|
2257
|
+
reasonChain.push(`Emergency: Using ${selectedModelId}`);
|
|
2258
|
+
}
|
|
2259
|
+
const selectedModel = this.config.models.find((m) => m.id === selectedModelId);
|
|
2260
|
+
const decisionTimeMs = Date.now() - startTime;
|
|
2261
|
+
const decision = {
|
|
2262
|
+
selectedModelId,
|
|
2263
|
+
selectedModelName: selectedModel?.name ?? "unknown",
|
|
2264
|
+
tier: selectedModel?.tier ?? this.config.defaultTier,
|
|
2265
|
+
phase1Results,
|
|
2266
|
+
phase2Results,
|
|
2267
|
+
reasonChain,
|
|
2268
|
+
totalCandidates: this.config.models.length,
|
|
2269
|
+
eligibleCandidates: eligibleIds.size,
|
|
2270
|
+
decisionTimeMs,
|
|
2271
|
+
fallbackUsed
|
|
2272
|
+
};
|
|
2273
|
+
if (this.config.onDecision) {
|
|
2274
|
+
try {
|
|
2275
|
+
await this.config.onDecision(decision);
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (this.config.onAudit) {
|
|
2280
|
+
try {
|
|
2281
|
+
await this.config.onAudit({
|
|
2282
|
+
type: "routing_decision",
|
|
2283
|
+
data: {
|
|
2284
|
+
selectedModelId,
|
|
2285
|
+
tier: decision.tier,
|
|
2286
|
+
eligibleCount: eligibleIds.size,
|
|
2287
|
+
totalCount: this.config.models.length,
|
|
2288
|
+
fallbackUsed,
|
|
2289
|
+
decisionTimeMs,
|
|
2290
|
+
taskType: request.taskType,
|
|
2291
|
+
riskLevel: request.riskLevel
|
|
2292
|
+
}
|
|
2293
|
+
});
|
|
2294
|
+
} catch {
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return decision;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Get models by tier
|
|
2301
|
+
*/
|
|
2302
|
+
getModelsByTier(tier) {
|
|
2303
|
+
return this.config.models.filter((m) => m.tier === tier && m.enabled);
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Update model status
|
|
2307
|
+
*/
|
|
2308
|
+
updateModel(modelId, updates) {
|
|
2309
|
+
const idx = this.config.models.findIndex((m) => m.id === modelId);
|
|
2310
|
+
if (idx === -1) return false;
|
|
2311
|
+
this.config.models[idx] = { ...this.config.models[idx], ...updates };
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Add a new hard rule
|
|
2316
|
+
*/
|
|
2317
|
+
addHardRule(rule) {
|
|
2318
|
+
this.config.hardRules.push(rule);
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Get routing explanation for a specific request (dry run)
|
|
2322
|
+
*/
|
|
2323
|
+
async explain(request) {
|
|
2324
|
+
const decision = await this.route(request);
|
|
2325
|
+
const lines = [
|
|
2326
|
+
`=== SOVR Routing Explanation ===`,
|
|
2327
|
+
`Task: ${request.taskType} | Risk: ${request.riskLevel}`,
|
|
2328
|
+
`Required Capabilities: ${request.requiredCapabilities.join(", ") || "none"}`,
|
|
2329
|
+
`Estimated Tokens: ${request.estimatedTokens}`,
|
|
2330
|
+
``,
|
|
2331
|
+
`--- Phase 1: Eligibility ---`
|
|
2332
|
+
];
|
|
2333
|
+
for (const r of decision.phase1Results) {
|
|
2334
|
+
const model = this.config.models.find((m) => m.id === r.candidateId);
|
|
2335
|
+
lines.push(
|
|
2336
|
+
` ${r.eligible ? "\u2705" : "\u274C"} ${model?.name ?? r.candidateId} [${r.reasonCode}] ${r.reasonDetail}`
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
lines.push("", `--- Phase 2: Scoring ---`);
|
|
2340
|
+
for (const r of decision.phase2Results) {
|
|
2341
|
+
const model = this.config.models.find((m) => m.id === r.candidateId);
|
|
2342
|
+
lines.push(
|
|
2343
|
+
` ${model?.name ?? r.candidateId}: ${r.score} (cost=${r.breakdown.costScore} lat=${r.breakdown.latencyScore} cap=${r.breakdown.capabilityScore})`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
lines.push(
|
|
2347
|
+
"",
|
|
2348
|
+
`--- Decision ---`,
|
|
2349
|
+
`Selected: ${decision.selectedModelName} (${decision.tier})`,
|
|
2350
|
+
`Fallback: ${decision.fallbackUsed ? "YES" : "NO"}`,
|
|
2351
|
+
`Time: ${decision.decisionTimeMs}ms`
|
|
2352
|
+
);
|
|
2353
|
+
return { decision, explanation: lines.join("\n") };
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
// src/timeSeriesAggregator.ts
|
|
2358
|
+
var DEFAULT_TSA_CONFIG = {
|
|
2359
|
+
windowSizeMs: 6e4,
|
|
2360
|
+
// 1 minute
|
|
2361
|
+
windowType: "tumbling",
|
|
2362
|
+
maxWindows: 60,
|
|
2363
|
+
// Keep 1 hour of 1-min windows
|
|
2364
|
+
maxValuesPerWindow: 1e4
|
|
2365
|
+
};
|
|
2366
|
+
function percentile(sorted, p) {
|
|
2367
|
+
if (sorted.length === 0) return 0;
|
|
2368
|
+
if (sorted.length === 1) return sorted[0];
|
|
2369
|
+
const idx = p / 100 * (sorted.length - 1);
|
|
2370
|
+
const lower = Math.floor(idx);
|
|
2371
|
+
const upper = Math.ceil(idx);
|
|
2372
|
+
if (lower === upper) return sorted[lower];
|
|
2373
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
|
|
2374
|
+
}
|
|
2375
|
+
var TimeSeriesAggregator = class {
|
|
2376
|
+
windows = /* @__PURE__ */ new Map();
|
|
2377
|
+
closedResults = [];
|
|
2378
|
+
config;
|
|
2379
|
+
totalDedups = 0;
|
|
2380
|
+
constructor(config) {
|
|
2381
|
+
this.config = { ...DEFAULT_TSA_CONFIG, ...config };
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Ingest a data point (idempotent — duplicates are rejected)
|
|
2385
|
+
*/
|
|
2386
|
+
async ingest(point) {
|
|
2387
|
+
const windowId = this.getWindowId(point.timestamp);
|
|
2388
|
+
let window = this.windows.get(windowId);
|
|
2389
|
+
if (!window) {
|
|
2390
|
+
window = this.createWindow(windowId, point.timestamp);
|
|
2391
|
+
this.windows.set(windowId, window);
|
|
2392
|
+
this.evictOldWindows();
|
|
2393
|
+
}
|
|
2394
|
+
if (window.seenIds.has(point.id)) {
|
|
2395
|
+
this.totalDedups++;
|
|
2396
|
+
return { accepted: false, windowId, duplicate: true };
|
|
2397
|
+
}
|
|
2398
|
+
window.seenIds.add(point.id);
|
|
2399
|
+
window.count++;
|
|
2400
|
+
window.sum += point.value;
|
|
2401
|
+
window.min = Math.min(window.min, point.value);
|
|
2402
|
+
window.max = Math.max(window.max, point.value);
|
|
2403
|
+
if (window.values.length < this.config.maxValuesPerWindow) {
|
|
2404
|
+
window.values.push(point.value);
|
|
2405
|
+
}
|
|
2406
|
+
return { accepted: true, windowId, duplicate: false };
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Ingest multiple points (batch)
|
|
2410
|
+
*/
|
|
2411
|
+
async ingestBatch(points) {
|
|
2412
|
+
let accepted = 0;
|
|
2413
|
+
let duplicates = 0;
|
|
2414
|
+
let errors = 0;
|
|
2415
|
+
for (const point of points) {
|
|
2416
|
+
try {
|
|
2417
|
+
const result = await this.ingest(point);
|
|
2418
|
+
if (result.accepted) accepted++;
|
|
2419
|
+
if (result.duplicate) duplicates++;
|
|
2420
|
+
} catch {
|
|
2421
|
+
errors++;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return { accepted, duplicates, errors };
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Get aggregation result for a specific window
|
|
2428
|
+
*/
|
|
2429
|
+
getWindowResult(windowId) {
|
|
2430
|
+
const window = this.windows.get(windowId);
|
|
2431
|
+
if (!window) {
|
|
2432
|
+
return this.closedResults.find((r) => r.windowId === windowId) ?? null;
|
|
2433
|
+
}
|
|
2434
|
+
return this.computeResult(window);
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Get current (latest) window result
|
|
2438
|
+
*/
|
|
2439
|
+
getCurrentResult() {
|
|
2440
|
+
const now = Date.now();
|
|
2441
|
+
const windowId = this.getWindowId(now);
|
|
2442
|
+
return this.getWindowResult(windowId);
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Get results for a time range
|
|
2446
|
+
*/
|
|
2447
|
+
getResultsInRange(startMs, endMs) {
|
|
2448
|
+
const results = [];
|
|
2449
|
+
for (const window of this.windows.values()) {
|
|
2450
|
+
if (window.startMs >= startMs && window.endMs <= endMs) {
|
|
2451
|
+
results.push(this.computeResult(window));
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
for (const result of this.closedResults) {
|
|
2455
|
+
if (result.startMs >= startMs && result.endMs <= endMs) {
|
|
2456
|
+
if (!results.find((r) => r.windowId === result.windowId)) {
|
|
2457
|
+
results.push(result);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return results.sort((a, b) => a.startMs - b.startMs);
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Close current window and emit result
|
|
2465
|
+
*/
|
|
2466
|
+
async closeWindow(windowId) {
|
|
2467
|
+
const window = this.windows.get(windowId);
|
|
2468
|
+
if (!window) return null;
|
|
2469
|
+
const result = this.computeResult(window);
|
|
2470
|
+
this.closedResults.push(result);
|
|
2471
|
+
this.windows.delete(windowId);
|
|
2472
|
+
if (this.closedResults.length > this.config.maxWindows * 2) {
|
|
2473
|
+
this.closedResults = this.closedResults.slice(-this.config.maxWindows);
|
|
2474
|
+
}
|
|
2475
|
+
if (this.config.onWindowClosed) {
|
|
2476
|
+
try {
|
|
2477
|
+
await this.config.onWindowClosed(result);
|
|
2478
|
+
} catch {
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (this.config.onAudit) {
|
|
2482
|
+
try {
|
|
2483
|
+
await this.config.onAudit({
|
|
2484
|
+
type: "window_closed",
|
|
2485
|
+
data: {
|
|
2486
|
+
windowId,
|
|
2487
|
+
count: result.metrics.count,
|
|
2488
|
+
avg: result.metrics.avg,
|
|
2489
|
+
p95: result.metrics.p95,
|
|
2490
|
+
dedupCount: result.dedupCount
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
} catch {
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return result;
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Get overall stats
|
|
2500
|
+
*/
|
|
2501
|
+
getStats() {
|
|
2502
|
+
let totalDataPoints = 0;
|
|
2503
|
+
for (const w of this.windows.values()) totalDataPoints += w.count;
|
|
2504
|
+
for (const r of this.closedResults) totalDataPoints += r.metrics.count;
|
|
2505
|
+
return {
|
|
2506
|
+
activeWindows: this.windows.size,
|
|
2507
|
+
closedWindows: this.closedResults.length,
|
|
2508
|
+
totalDedups: this.totalDedups,
|
|
2509
|
+
totalDataPoints
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Reset all windows
|
|
2514
|
+
*/
|
|
2515
|
+
reset() {
|
|
2516
|
+
this.windows.clear();
|
|
2517
|
+
this.closedResults = [];
|
|
2518
|
+
this.totalDedups = 0;
|
|
2519
|
+
}
|
|
2520
|
+
// ─── Internal ────────────────────────────────────────────────────
|
|
2521
|
+
getWindowId(timestamp) {
|
|
2522
|
+
const windowStart = Math.floor(timestamp / this.config.windowSizeMs) * this.config.windowSizeMs;
|
|
2523
|
+
return `w_${windowStart}`;
|
|
2524
|
+
}
|
|
2525
|
+
createWindow(windowId, timestamp) {
|
|
2526
|
+
const windowStart = Math.floor(timestamp / this.config.windowSizeMs) * this.config.windowSizeMs;
|
|
2527
|
+
return {
|
|
2528
|
+
windowId,
|
|
2529
|
+
windowType: this.config.windowType,
|
|
2530
|
+
startMs: windowStart,
|
|
2531
|
+
endMs: windowStart + this.config.windowSizeMs,
|
|
2532
|
+
count: 0,
|
|
2533
|
+
sum: 0,
|
|
2534
|
+
min: Infinity,
|
|
2535
|
+
max: -Infinity,
|
|
2536
|
+
values: [],
|
|
2537
|
+
seenIds: /* @__PURE__ */ new Set()
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
computeResult(window) {
|
|
2541
|
+
const sorted = [...window.values].sort((a, b) => a - b);
|
|
2542
|
+
const durationSec = (window.endMs - window.startMs) / 1e3;
|
|
2543
|
+
return {
|
|
2544
|
+
windowId: window.windowId,
|
|
2545
|
+
windowType: window.windowType,
|
|
2546
|
+
startMs: window.startMs,
|
|
2547
|
+
endMs: window.endMs,
|
|
2548
|
+
metrics: {
|
|
2549
|
+
count: window.count,
|
|
2550
|
+
sum: window.sum,
|
|
2551
|
+
avg: window.count > 0 ? window.sum / window.count : 0,
|
|
2552
|
+
min: window.min === Infinity ? 0 : window.min,
|
|
2553
|
+
max: window.max === -Infinity ? 0 : window.max,
|
|
2554
|
+
p50: percentile(sorted, 50),
|
|
2555
|
+
p95: percentile(sorted, 95),
|
|
2556
|
+
p99: percentile(sorted, 99),
|
|
2557
|
+
rate: durationSec > 0 ? window.count / durationSec : 0
|
|
2558
|
+
},
|
|
2559
|
+
dedupCount: window.seenIds.size - window.count > 0 ? 0 : this.totalDedups
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
evictOldWindows() {
|
|
2563
|
+
if (this.windows.size <= this.config.maxWindows) return;
|
|
2564
|
+
const sorted = Array.from(this.windows.entries()).sort((a, b) => a[1].startMs - b[1].startMs);
|
|
2565
|
+
while (sorted.length > this.config.maxWindows) {
|
|
2566
|
+
const [id, window] = sorted.shift();
|
|
2567
|
+
this.closedResults.push(this.computeResult(window));
|
|
2568
|
+
this.windows.delete(id);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
|
|
2573
|
+
// src/valuationModel.ts
|
|
2574
|
+
var ValuationModel = class {
|
|
2575
|
+
config;
|
|
2576
|
+
constructor(config) {
|
|
2577
|
+
this.config = config ?? {};
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Calculate TAM/SAM/SOM
|
|
2581
|
+
*/
|
|
2582
|
+
calculateMarketSize(input) {
|
|
2583
|
+
return {
|
|
2584
|
+
tam: input.totalMarketUsd,
|
|
2585
|
+
sam: input.totalMarketUsd * input.serviceablePercent,
|
|
2586
|
+
som: input.totalMarketUsd * input.serviceablePercent * input.obtainablePercent,
|
|
2587
|
+
year: input.year,
|
|
2588
|
+
growthRate: input.growthRate,
|
|
2589
|
+
assumptions: input.assumptions
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Calculate Unit Economics
|
|
2594
|
+
*/
|
|
2595
|
+
calculateUnitEconomics(input) {
|
|
2596
|
+
const netChurn = input.churnRate - input.expansionRate;
|
|
2597
|
+
const effectiveChurn = Math.max(netChurn, 1e-3);
|
|
2598
|
+
const ltv = input.arpu * input.grossMargin / effectiveChurn;
|
|
2599
|
+
const paybackMonths = input.cac / (input.arpu * input.grossMargin);
|
|
2600
|
+
return {
|
|
2601
|
+
arpu: input.arpu,
|
|
2602
|
+
cac: input.cac,
|
|
2603
|
+
ltv: Math.round(ltv * 100) / 100,
|
|
2604
|
+
ltvCacRatio: Math.round(ltv / input.cac * 100) / 100,
|
|
2605
|
+
paybackMonths: Math.round(paybackMonths * 10) / 10,
|
|
2606
|
+
grossMargin: input.grossMargin,
|
|
2607
|
+
churnRate: input.churnRate,
|
|
2608
|
+
expansionRate: input.expansionRate
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* DCF Valuation
|
|
2613
|
+
*/
|
|
2614
|
+
calculateDCF(input) {
|
|
2615
|
+
const fcfs = [];
|
|
2616
|
+
const discountedFcfs = [];
|
|
2617
|
+
for (let i = 0; i < input.projectedRevenues.length; i++) {
|
|
2618
|
+
const revenue = input.projectedRevenues[i];
|
|
2619
|
+
const operatingIncome = revenue * input.operatingMargin;
|
|
2620
|
+
const afterTax = operatingIncome * (1 - input.taxRate);
|
|
2621
|
+
const capex = revenue * input.capexRatio;
|
|
2622
|
+
const fcf = afterTax - capex;
|
|
2623
|
+
fcfs.push(fcf);
|
|
2624
|
+
const discountFactor = Math.pow(1 + input.discountRate, i + 1);
|
|
2625
|
+
discountedFcfs.push(fcf / discountFactor);
|
|
2626
|
+
}
|
|
2627
|
+
const presentValue = discountedFcfs.reduce((sum, v) => sum + v, 0);
|
|
2628
|
+
const lastFcf = fcfs[fcfs.length - 1] ?? 0;
|
|
2629
|
+
const terminalFcf = lastFcf * (1 + input.terminalGrowthRate);
|
|
2630
|
+
const terminalValue = terminalFcf / (input.discountRate - input.terminalGrowthRate);
|
|
2631
|
+
const discountedTerminal = terminalValue / Math.pow(1 + input.discountRate, input.projectedRevenues.length);
|
|
2632
|
+
const enterpriseValue = presentValue + discountedTerminal;
|
|
2633
|
+
const lastRevenue = input.projectedRevenues[input.projectedRevenues.length - 1] ?? 1;
|
|
2634
|
+
const impliedMultiple = enterpriseValue / lastRevenue;
|
|
2635
|
+
return {
|
|
2636
|
+
presentValue: Math.round(presentValue),
|
|
2637
|
+
terminalValue: Math.round(discountedTerminal),
|
|
2638
|
+
enterpriseValue: Math.round(enterpriseValue),
|
|
2639
|
+
freeCashFlows: fcfs.map((v) => Math.round(v)),
|
|
2640
|
+
discountedCashFlows: discountedFcfs.map((v) => Math.round(v)),
|
|
2641
|
+
impliedMultiple: Math.round(impliedMultiple * 10) / 10
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Comparable Company Valuation
|
|
2646
|
+
*/
|
|
2647
|
+
calculateComparable(input) {
|
|
2648
|
+
const revMultiples = input.comparables.map((c) => c.evRevenue);
|
|
2649
|
+
const ebitdaMultiples = input.comparables.map((c) => c.evEbitda);
|
|
2650
|
+
const userValues = input.comparables.map((c) => c.evUser);
|
|
2651
|
+
const medianRevMultiple = this.median(revMultiples);
|
|
2652
|
+
const medianEbitdaMultiple = this.median(ebitdaMultiples);
|
|
2653
|
+
const medianUserValue = this.median(userValues);
|
|
2654
|
+
const revenueVal = input.revenue * medianRevMultiple;
|
|
2655
|
+
const ebitdaVal = input.ebitda * medianEbitdaMultiple;
|
|
2656
|
+
const userVal = input.users * medianUserValue;
|
|
2657
|
+
const avgCompGrowth = input.comparables.reduce((s, c) => s + c.growthRate, 0) / input.comparables.length;
|
|
2658
|
+
const growthPremium = input.growthRate / (avgCompGrowth || 0.1);
|
|
2659
|
+
const growthAdjusted = revenueVal * Math.min(growthPremium, 3);
|
|
2660
|
+
return {
|
|
2661
|
+
revenueMultipleValuation: Math.round(revenueVal),
|
|
2662
|
+
ebitdaMultipleValuation: Math.round(ebitdaVal),
|
|
2663
|
+
perUserValuation: Math.round(userVal),
|
|
2664
|
+
averageValuation: Math.round((revenueVal + ebitdaVal + userVal) / 3),
|
|
2665
|
+
medianMultiple: medianRevMultiple,
|
|
2666
|
+
growthAdjustedValuation: Math.round(growthAdjusted)
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
/**
|
|
2670
|
+
* Network Effect Valuation (Metcalfe's Law)
|
|
2671
|
+
*/
|
|
2672
|
+
calculateNetworkEffect(input) {
|
|
2673
|
+
const currentValue = Math.pow(input.currentUsers, input.metcalfeExponent) * input.valuePerConnection * input.networkDensity;
|
|
2674
|
+
const projectedValues = input.projectedUsers.map(
|
|
2675
|
+
(users) => Math.pow(users, input.metcalfeExponent) * input.valuePerConnection * input.networkDensity
|
|
2676
|
+
);
|
|
2677
|
+
const metcalfeValue = Math.pow(input.currentUsers, 2) * input.valuePerConnection;
|
|
2678
|
+
const adjustedValue = metcalfeValue * input.networkDensity;
|
|
2679
|
+
return {
|
|
2680
|
+
currentNetworkValue: Math.round(currentValue),
|
|
2681
|
+
projectedValues: projectedValues.map((v) => Math.round(v)),
|
|
2682
|
+
metcalfeValue: Math.round(metcalfeValue),
|
|
2683
|
+
adjustedValue: Math.round(adjustedValue)
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Sensitivity Analysis
|
|
2688
|
+
*/
|
|
2689
|
+
runSensitivity(baseInput, variables) {
|
|
2690
|
+
const baseResult = this.calculateDCF(baseInput);
|
|
2691
|
+
const baseCase = baseResult.enterpriseValue;
|
|
2692
|
+
const scenarios = [];
|
|
2693
|
+
const tornado = [];
|
|
2694
|
+
for (const v of variables) {
|
|
2695
|
+
const lowInput = { ...baseInput, [v.field]: v.low };
|
|
2696
|
+
const lowResult = this.calculateDCF(lowInput);
|
|
2697
|
+
const highInput = { ...baseInput, [v.field]: v.high };
|
|
2698
|
+
const highResult = this.calculateDCF(highInput);
|
|
2699
|
+
scenarios.push({
|
|
2700
|
+
name: `${v.name} Low`,
|
|
2701
|
+
value: lowResult.enterpriseValue,
|
|
2702
|
+
delta: (lowResult.enterpriseValue - baseCase) / baseCase * 100,
|
|
2703
|
+
variables: { [v.name]: v.low }
|
|
2704
|
+
});
|
|
2705
|
+
scenarios.push({
|
|
2706
|
+
name: `${v.name} High`,
|
|
2707
|
+
value: highResult.enterpriseValue,
|
|
2708
|
+
delta: (highResult.enterpriseValue - baseCase) / baseCase * 100,
|
|
2709
|
+
variables: { [v.name]: v.high }
|
|
2710
|
+
});
|
|
2711
|
+
tornado.push({
|
|
2712
|
+
variable: v.name,
|
|
2713
|
+
lowValue: lowResult.enterpriseValue,
|
|
2714
|
+
highValue: highResult.enterpriseValue,
|
|
2715
|
+
range: Math.abs(highResult.enterpriseValue - lowResult.enterpriseValue)
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
tornado.sort((a, b) => b.range - a.range);
|
|
2719
|
+
return { baseCase, scenarios, tornado };
|
|
2720
|
+
}
|
|
2721
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
2722
|
+
median(values) {
|
|
2723
|
+
if (values.length === 0) return 0;
|
|
2724
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2725
|
+
const mid = Math.floor(sorted.length / 2);
|
|
2726
|
+
return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2730
|
+
// src/governanceEnhancer.ts
|
|
2731
|
+
var DEFAULT_GE_CONFIG = {
|
|
2732
|
+
circuitBreakerThreshold: 5,
|
|
2733
|
+
circuitBreakerTimeoutMs: 3e4,
|
|
2734
|
+
halfOpenSuccessThreshold: 3,
|
|
2735
|
+
degradationChain: [
|
|
2736
|
+
{
|
|
2737
|
+
id: "full",
|
|
2738
|
+
name: "Full Capability",
|
|
2739
|
+
order: 4,
|
|
2740
|
+
capabilities: ["all"],
|
|
2741
|
+
restrictions: []
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
id: "reduced",
|
|
2745
|
+
name: "Reduced Capability",
|
|
2746
|
+
order: 3,
|
|
2747
|
+
capabilities: ["read", "compute", "cache"],
|
|
2748
|
+
restrictions: ["write", "external_api"]
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
id: "minimal",
|
|
2752
|
+
name: "Minimal Capability",
|
|
2753
|
+
order: 2,
|
|
2754
|
+
capabilities: ["read", "cache"],
|
|
2755
|
+
restrictions: ["write", "compute", "external_api"]
|
|
2756
|
+
},
|
|
2757
|
+
{
|
|
2758
|
+
id: "readonly",
|
|
2759
|
+
name: "Read-Only Mode",
|
|
2760
|
+
order: 1,
|
|
2761
|
+
capabilities: ["read"],
|
|
2762
|
+
restrictions: ["write", "compute", "external_api", "cache"]
|
|
2763
|
+
}
|
|
2764
|
+
],
|
|
2765
|
+
maxPolicyVersions: 10
|
|
2766
|
+
};
|
|
2767
|
+
function simpleHash2(input) {
|
|
2768
|
+
let hash = 0;
|
|
2769
|
+
for (let i = 0; i < input.length; i++) {
|
|
2770
|
+
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
2771
|
+
hash = hash & hash;
|
|
2772
|
+
}
|
|
2773
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
2774
|
+
}
|
|
2775
|
+
var GovernanceEnhancer = class {
|
|
2776
|
+
currentPolicy = null;
|
|
2777
|
+
policyHistory = [];
|
|
2778
|
+
circuitBreakers = /* @__PURE__ */ new Map();
|
|
2779
|
+
currentDegradationLevel = "full";
|
|
2780
|
+
config;
|
|
2781
|
+
constructor(config) {
|
|
2782
|
+
this.config = { ...DEFAULT_GE_CONFIG, ...config };
|
|
2783
|
+
}
|
|
2784
|
+
// ─── Policy Hot-Reload ───────────────────────────────────────────
|
|
2785
|
+
/**
|
|
2786
|
+
* Load a new policy version (hot-reload)
|
|
2787
|
+
*/
|
|
2788
|
+
async loadPolicy(version, rules) {
|
|
2789
|
+
const hash = simpleHash2(JSON.stringify(rules));
|
|
2790
|
+
const oldVersion = this.currentPolicy?.version;
|
|
2791
|
+
const newPolicy = {
|
|
2792
|
+
version,
|
|
2793
|
+
rules: rules.sort((a, b) => b.priority - a.priority),
|
|
2794
|
+
activatedAt: Date.now(),
|
|
2795
|
+
hash
|
|
2796
|
+
};
|
|
2797
|
+
if (this.currentPolicy) {
|
|
2798
|
+
this.policyHistory.push(this.currentPolicy);
|
|
2799
|
+
if (this.policyHistory.length > this.config.maxPolicyVersions) {
|
|
2800
|
+
this.policyHistory = this.policyHistory.slice(-this.config.maxPolicyVersions);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
this.currentPolicy = newPolicy;
|
|
2804
|
+
if (this.config.onPolicyChange && oldVersion) {
|
|
2805
|
+
try {
|
|
2806
|
+
await this.config.onPolicyChange(oldVersion, version);
|
|
2807
|
+
} catch {
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
if (this.config.onAudit) {
|
|
2811
|
+
try {
|
|
2812
|
+
await this.config.onAudit({
|
|
2813
|
+
type: "policy_loaded",
|
|
2814
|
+
data: { version, ruleCount: rules.length, hash, previousVersion: oldVersion }
|
|
2815
|
+
});
|
|
2816
|
+
} catch {
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
return { success: true, previousVersion: oldVersion, newVersion: version };
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Rollback to a previous policy version
|
|
2823
|
+
*/
|
|
2824
|
+
async rollbackPolicy(version) {
|
|
2825
|
+
const target = this.policyHistory.find((p) => p.version === version);
|
|
2826
|
+
if (!target) return false;
|
|
2827
|
+
if (this.currentPolicy) {
|
|
2828
|
+
this.policyHistory.push(this.currentPolicy);
|
|
2829
|
+
}
|
|
2830
|
+
this.currentPolicy = { ...target, activatedAt: Date.now() };
|
|
2831
|
+
return true;
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Evaluate a governance decision
|
|
2835
|
+
*/
|
|
2836
|
+
async evaluate(context) {
|
|
2837
|
+
if (!this.currentPolicy) {
|
|
2838
|
+
return {
|
|
2839
|
+
allowed: false,
|
|
2840
|
+
action: "deny",
|
|
2841
|
+
reason: "No policy loaded",
|
|
2842
|
+
policyVersion: "none"
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
const cbState = this.getCircuitBreakerState(context.resource);
|
|
2846
|
+
if (cbState === "OPEN") {
|
|
2847
|
+
return {
|
|
2848
|
+
allowed: false,
|
|
2849
|
+
action: "deny",
|
|
2850
|
+
circuitState: "OPEN",
|
|
2851
|
+
reason: `Circuit breaker OPEN for ${context.resource}`,
|
|
2852
|
+
policyVersion: this.currentPolicy.version
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
for (const rule of this.currentPolicy.rules) {
|
|
2856
|
+
if (!rule.enabled) continue;
|
|
2857
|
+
const matches = this.evaluateCondition(rule.condition, context);
|
|
2858
|
+
if (!matches) continue;
|
|
2859
|
+
const decision = {
|
|
2860
|
+
allowed: rule.action === "allow",
|
|
2861
|
+
action: rule.action,
|
|
2862
|
+
ruleId: rule.id,
|
|
2863
|
+
ruleName: rule.name,
|
|
2864
|
+
circuitState: cbState,
|
|
2865
|
+
policyVersion: this.currentPolicy.version,
|
|
2866
|
+
reason: `Rule ${rule.name} matched: ${rule.action}`
|
|
2867
|
+
};
|
|
2868
|
+
if (rule.action === "degrade" && rule.degradeTo) {
|
|
2869
|
+
decision.degradationLevel = rule.degradeTo;
|
|
2870
|
+
decision.allowed = true;
|
|
2871
|
+
this.currentDegradationLevel = rule.degradeTo;
|
|
2872
|
+
}
|
|
2873
|
+
if (this.config.onAudit) {
|
|
2874
|
+
try {
|
|
2875
|
+
await this.config.onAudit({
|
|
2876
|
+
type: "governance_decision",
|
|
2877
|
+
data: {
|
|
2878
|
+
action: context.action,
|
|
2879
|
+
resource: context.resource,
|
|
2880
|
+
decision: rule.action,
|
|
2881
|
+
ruleId: rule.id,
|
|
2882
|
+
policyVersion: this.currentPolicy.version
|
|
2883
|
+
}
|
|
2884
|
+
});
|
|
2885
|
+
} catch {
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return decision;
|
|
2889
|
+
}
|
|
2890
|
+
return {
|
|
2891
|
+
allowed: true,
|
|
2892
|
+
action: "allow",
|
|
2893
|
+
reason: "No matching rule, default allow",
|
|
2894
|
+
policyVersion: this.currentPolicy.version,
|
|
2895
|
+
circuitState: cbState
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
// ─── Circuit Breaker ─────────────────────────────────────────────
|
|
2899
|
+
/**
|
|
2900
|
+
* Record a success for circuit breaker
|
|
2901
|
+
*/
|
|
2902
|
+
recordSuccess(resource) {
|
|
2903
|
+
const cb = this.getOrCreateCircuitBreaker(resource);
|
|
2904
|
+
cb.successCount++;
|
|
2905
|
+
cb.lastSuccessAt = Date.now();
|
|
2906
|
+
if (cb.state === "HALF_OPEN") {
|
|
2907
|
+
if (cb.successCount >= this.config.halfOpenSuccessThreshold) {
|
|
2908
|
+
cb.state = "CLOSED";
|
|
2909
|
+
cb.failureCount = 0;
|
|
2910
|
+
cb.halfOpenAttempts = 0;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Record a failure for circuit breaker
|
|
2916
|
+
*/
|
|
2917
|
+
recordFailure(resource) {
|
|
2918
|
+
const cb = this.getOrCreateCircuitBreaker(resource);
|
|
2919
|
+
cb.failureCount++;
|
|
2920
|
+
cb.lastFailureAt = Date.now();
|
|
2921
|
+
if (cb.state === "CLOSED" && cb.failureCount >= this.config.circuitBreakerThreshold) {
|
|
2922
|
+
cb.state = "OPEN";
|
|
2923
|
+
cb.openedAt = Date.now();
|
|
2924
|
+
} else if (cb.state === "HALF_OPEN") {
|
|
2925
|
+
cb.state = "OPEN";
|
|
2926
|
+
cb.openedAt = Date.now();
|
|
2927
|
+
cb.halfOpenAttempts++;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Get circuit breaker state for a resource
|
|
2932
|
+
*/
|
|
2933
|
+
getCircuitBreakerState(resource) {
|
|
2934
|
+
const cb = this.circuitBreakers.get(resource);
|
|
2935
|
+
if (!cb) return "CLOSED";
|
|
2936
|
+
if (cb.state === "OPEN") {
|
|
2937
|
+
const elapsed = Date.now() - cb.openedAt;
|
|
2938
|
+
if (elapsed >= this.config.circuitBreakerTimeoutMs) {
|
|
2939
|
+
cb.state = "HALF_OPEN";
|
|
2940
|
+
cb.successCount = 0;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
return cb.state;
|
|
2944
|
+
}
|
|
2945
|
+
// ─── Degradation Chain ───────────────────────────────────────────
|
|
2946
|
+
/**
|
|
2947
|
+
* Get current degradation level
|
|
2948
|
+
*/
|
|
2949
|
+
getCurrentDegradationLevel() {
|
|
2950
|
+
return this.config.degradationChain.find((d) => d.id === this.currentDegradationLevel);
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Degrade to next level
|
|
2954
|
+
*/
|
|
2955
|
+
degradeOneLevel() {
|
|
2956
|
+
const current = this.config.degradationChain.find((d) => d.id === this.currentDegradationLevel);
|
|
2957
|
+
if (!current) return null;
|
|
2958
|
+
const nextLevel = this.config.degradationChain.filter((d) => d.order < current.order).sort((a, b) => b.order - a.order)[0];
|
|
2959
|
+
if (nextLevel) {
|
|
2960
|
+
this.currentDegradationLevel = nextLevel.id;
|
|
2961
|
+
return nextLevel;
|
|
2962
|
+
}
|
|
2963
|
+
return null;
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Restore to full capability
|
|
2967
|
+
*/
|
|
2968
|
+
restoreFullCapability() {
|
|
2969
|
+
this.currentDegradationLevel = "full";
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Check if a capability is available at current degradation level
|
|
2973
|
+
*/
|
|
2974
|
+
isCapabilityAvailable(capability) {
|
|
2975
|
+
const level = this.getCurrentDegradationLevel();
|
|
2976
|
+
if (!level) return false;
|
|
2977
|
+
return level.capabilities.includes("all") || level.capabilities.includes(capability);
|
|
2978
|
+
}
|
|
2979
|
+
// ─── Status ──────────────────────────────────────────────────────
|
|
2980
|
+
/**
|
|
2981
|
+
* Get full governance status
|
|
2982
|
+
*/
|
|
2983
|
+
getStatus() {
|
|
2984
|
+
return {
|
|
2985
|
+
policyVersion: this.currentPolicy?.version ?? null,
|
|
2986
|
+
ruleCount: this.currentPolicy?.rules.length ?? 0,
|
|
2987
|
+
degradationLevel: this.currentDegradationLevel,
|
|
2988
|
+
circuitBreakers: Array.from(this.circuitBreakers.values()),
|
|
2989
|
+
policyHistoryCount: this.policyHistory.length
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
// ─── Internal ────────────────────────────────────────────────────
|
|
2993
|
+
evaluateCondition(condition, context) {
|
|
2994
|
+
if (condition === "*") return true;
|
|
2995
|
+
if (condition.startsWith("action:")) {
|
|
2996
|
+
return context.action === condition.substring(7);
|
|
2997
|
+
}
|
|
2998
|
+
if (condition.startsWith("resource:")) {
|
|
2999
|
+
return context.resource.includes(condition.substring(9));
|
|
3000
|
+
}
|
|
3001
|
+
if (condition.startsWith("agent:")) {
|
|
3002
|
+
return context.agentId === condition.substring(6);
|
|
3003
|
+
}
|
|
3004
|
+
return false;
|
|
3005
|
+
}
|
|
3006
|
+
getOrCreateCircuitBreaker(resource) {
|
|
3007
|
+
let cb = this.circuitBreakers.get(resource);
|
|
3008
|
+
if (!cb) {
|
|
3009
|
+
cb = {
|
|
3010
|
+
name: resource,
|
|
3011
|
+
state: "CLOSED",
|
|
3012
|
+
failureCount: 0,
|
|
3013
|
+
successCount: 0,
|
|
3014
|
+
lastFailureAt: 0,
|
|
3015
|
+
lastSuccessAt: 0,
|
|
3016
|
+
openedAt: 0,
|
|
3017
|
+
halfOpenAttempts: 0
|
|
3018
|
+
};
|
|
3019
|
+
this.circuitBreakers.set(resource, cb);
|
|
3020
|
+
}
|
|
3021
|
+
return cb;
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
|
|
3025
|
+
// src/contextAccelerator.ts
|
|
3026
|
+
var DEFAULT_CA_CONFIG = {
|
|
3027
|
+
maxCacheEntries: 500,
|
|
3028
|
+
defaultTtlMs: 3e5,
|
|
3029
|
+
// 5 minutes
|
|
3030
|
+
tokenBudget: 8e3,
|
|
3031
|
+
compressionTarget: 0.5,
|
|
3032
|
+
priorityWeights: {
|
|
3033
|
+
critical: 100,
|
|
3034
|
+
high: 75,
|
|
3035
|
+
medium: 50,
|
|
3036
|
+
low: 25
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
function estimateTokens(text) {
|
|
3040
|
+
return Math.ceil(text.length / 3.5);
|
|
3041
|
+
}
|
|
3042
|
+
function defaultCompressor(content, targetTokens) {
|
|
3043
|
+
const currentTokens = estimateTokens(content);
|
|
3044
|
+
if (currentTokens <= targetTokens) return content;
|
|
3045
|
+
const ratio = targetTokens / currentTokens;
|
|
3046
|
+
const targetLength = Math.floor(content.length * ratio);
|
|
3047
|
+
if (targetLength < 50) return content.substring(0, 50) + "...";
|
|
3048
|
+
return content.substring(0, targetLength) + "...[compressed]";
|
|
3049
|
+
}
|
|
3050
|
+
var ContextAccelerator = class {
|
|
3051
|
+
cache = /* @__PURE__ */ new Map();
|
|
3052
|
+
prefetchRules = /* @__PURE__ */ new Map();
|
|
3053
|
+
config;
|
|
3054
|
+
stats = {
|
|
3055
|
+
totalHits: 0,
|
|
3056
|
+
totalMisses: 0,
|
|
3057
|
+
totalEvictions: 0,
|
|
3058
|
+
totalCompressions: 0,
|
|
3059
|
+
totalAssemblies: 0
|
|
3060
|
+
};
|
|
3061
|
+
constructor(config) {
|
|
3062
|
+
this.config = { ...DEFAULT_CA_CONFIG, ...config };
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Put a fragment into cache
|
|
3066
|
+
*/
|
|
3067
|
+
put(fragment) {
|
|
3068
|
+
this.evictExpired();
|
|
3069
|
+
if (this.cache.size >= this.config.maxCacheEntries) {
|
|
3070
|
+
this.evictLRU();
|
|
3071
|
+
}
|
|
3072
|
+
this.cache.set(fragment.id, {
|
|
3073
|
+
fragment,
|
|
3074
|
+
accessCount: 0,
|
|
3075
|
+
lastAccessedAt: Date.now(),
|
|
3076
|
+
createdAt: Date.now(),
|
|
3077
|
+
expiresAt: Date.now() + (fragment.ttlMs || this.config.defaultTtlMs)
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Get a fragment from cache
|
|
3082
|
+
*/
|
|
3083
|
+
get(id) {
|
|
3084
|
+
const entry = this.cache.get(id);
|
|
3085
|
+
if (!entry) {
|
|
3086
|
+
this.stats.totalMisses++;
|
|
3087
|
+
return null;
|
|
3088
|
+
}
|
|
3089
|
+
if (Date.now() > entry.expiresAt) {
|
|
3090
|
+
this.cache.delete(id);
|
|
3091
|
+
this.stats.totalMisses++;
|
|
3092
|
+
return null;
|
|
3093
|
+
}
|
|
3094
|
+
entry.accessCount++;
|
|
3095
|
+
entry.lastAccessedAt = Date.now();
|
|
3096
|
+
this.stats.totalHits++;
|
|
3097
|
+
return entry.fragment;
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Assemble context from fragments within token budget
|
|
3101
|
+
*/
|
|
3102
|
+
async assemble(fragmentIds, additionalFragments, budgetOverride) {
|
|
3103
|
+
const startTime = Date.now();
|
|
3104
|
+
const budget = budgetOverride ?? this.config.tokenBudget;
|
|
3105
|
+
let cacheHits = 0;
|
|
3106
|
+
let cacheMisses = 0;
|
|
3107
|
+
const allFragments = [];
|
|
3108
|
+
for (const id of fragmentIds) {
|
|
3109
|
+
const fragment = this.get(id);
|
|
3110
|
+
if (fragment) {
|
|
3111
|
+
allFragments.push(fragment);
|
|
3112
|
+
cacheHits++;
|
|
3113
|
+
} else {
|
|
3114
|
+
cacheMisses++;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
if (additionalFragments) {
|
|
3118
|
+
allFragments.push(...additionalFragments);
|
|
3119
|
+
}
|
|
3120
|
+
const weights = this.config.priorityWeights;
|
|
3121
|
+
allFragments.sort((a, b) => {
|
|
3122
|
+
const wDiff = (weights[b.priority] ?? 0) - (weights[a.priority] ?? 0);
|
|
3123
|
+
if (wDiff !== 0) return wDiff;
|
|
3124
|
+
return b.timestamp - a.timestamp;
|
|
3125
|
+
});
|
|
3126
|
+
const selected = [];
|
|
3127
|
+
let totalTokens = 0;
|
|
3128
|
+
let droppedCount = 0;
|
|
3129
|
+
let compressedCount = 0;
|
|
3130
|
+
for (const fragment of allFragments) {
|
|
3131
|
+
if (totalTokens >= budget) {
|
|
3132
|
+
droppedCount++;
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
const remaining = budget - totalTokens;
|
|
3136
|
+
if (fragment.tokenCount <= remaining) {
|
|
3137
|
+
selected.push(fragment);
|
|
3138
|
+
totalTokens += fragment.tokenCount;
|
|
3139
|
+
} else if (fragment.priority === "critical" || fragment.priority === "high") {
|
|
3140
|
+
const compressor = this.config.compressor ?? defaultCompressor;
|
|
3141
|
+
const compressed = compressor(fragment.content, remaining);
|
|
3142
|
+
const compressedTokens = estimateTokens(compressed);
|
|
3143
|
+
selected.push({
|
|
3144
|
+
...fragment,
|
|
3145
|
+
content: compressed,
|
|
3146
|
+
tokenCount: compressedTokens,
|
|
3147
|
+
compressed: true
|
|
3148
|
+
});
|
|
3149
|
+
totalTokens += compressedTokens;
|
|
3150
|
+
compressedCount++;
|
|
3151
|
+
this.stats.totalCompressions++;
|
|
3152
|
+
} else {
|
|
3153
|
+
droppedCount++;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
this.stats.totalAssemblies++;
|
|
3157
|
+
const result = {
|
|
3158
|
+
fragments: selected,
|
|
3159
|
+
totalTokens,
|
|
3160
|
+
budgetUsed: budget > 0 ? totalTokens / budget : 0,
|
|
3161
|
+
droppedCount,
|
|
3162
|
+
compressedCount,
|
|
3163
|
+
cacheHits,
|
|
3164
|
+
cacheMisses,
|
|
3165
|
+
assemblyTimeMs: Date.now() - startTime
|
|
3166
|
+
};
|
|
3167
|
+
if (this.config.onAudit) {
|
|
3168
|
+
try {
|
|
3169
|
+
await this.config.onAudit({
|
|
3170
|
+
type: "context_assembled",
|
|
3171
|
+
data: {
|
|
3172
|
+
totalTokens,
|
|
3173
|
+
budgetUsed: result.budgetUsed,
|
|
3174
|
+
fragmentCount: selected.length,
|
|
3175
|
+
droppedCount,
|
|
3176
|
+
compressedCount,
|
|
3177
|
+
cacheHits,
|
|
3178
|
+
cacheMisses,
|
|
3179
|
+
assemblyTimeMs: result.assemblyTimeMs
|
|
3180
|
+
}
|
|
3181
|
+
});
|
|
3182
|
+
} catch {
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
return result;
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* Register a prefetch rule
|
|
3189
|
+
*/
|
|
3190
|
+
addPrefetchRule(rule) {
|
|
3191
|
+
this.prefetchRules.set(rule.id, rule);
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Execute prefetch based on task type
|
|
3195
|
+
*/
|
|
3196
|
+
async prefetch(taskType, fragmentLoader) {
|
|
3197
|
+
const matchingRules = Array.from(this.prefetchRules.values()).filter(
|
|
3198
|
+
(r) => r.enabled && taskType.includes(r.trigger)
|
|
3199
|
+
);
|
|
3200
|
+
if (matchingRules.length === 0) return 0;
|
|
3201
|
+
const idsToFetch = /* @__PURE__ */ new Set();
|
|
3202
|
+
for (const rule of matchingRules) {
|
|
3203
|
+
for (const id of rule.fragmentIds) {
|
|
3204
|
+
if (!this.cache.has(id)) {
|
|
3205
|
+
idsToFetch.add(id);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
if (idsToFetch.size === 0) return 0;
|
|
3210
|
+
const fragments = await fragmentLoader(Array.from(idsToFetch));
|
|
3211
|
+
for (const f of fragments) {
|
|
3212
|
+
this.put(f);
|
|
3213
|
+
}
|
|
3214
|
+
return fragments.length;
|
|
3215
|
+
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Invalidate cache entries
|
|
3218
|
+
*/
|
|
3219
|
+
invalidate(ids) {
|
|
3220
|
+
let count = 0;
|
|
3221
|
+
for (const id of ids) {
|
|
3222
|
+
if (this.cache.delete(id)) count++;
|
|
3223
|
+
}
|
|
3224
|
+
return count;
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Get cache stats
|
|
3228
|
+
*/
|
|
3229
|
+
getStats() {
|
|
3230
|
+
const total = this.stats.totalHits + this.stats.totalMisses;
|
|
3231
|
+
return {
|
|
3232
|
+
cacheSize: this.cache.size,
|
|
3233
|
+
maxSize: this.config.maxCacheEntries,
|
|
3234
|
+
hitRate: total > 0 ? this.stats.totalHits / total : 0,
|
|
3235
|
+
...this.stats,
|
|
3236
|
+
prefetchRuleCount: this.prefetchRules.size
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Clear all cache
|
|
3241
|
+
*/
|
|
3242
|
+
clear() {
|
|
3243
|
+
this.cache.clear();
|
|
3244
|
+
}
|
|
3245
|
+
// ─── Internal ────────────────────────────────────────────────────
|
|
3246
|
+
evictExpired() {
|
|
3247
|
+
const now = Date.now();
|
|
3248
|
+
for (const [id, entry] of this.cache) {
|
|
3249
|
+
if (now > entry.expiresAt) {
|
|
3250
|
+
this.cache.delete(id);
|
|
3251
|
+
this.stats.totalEvictions++;
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
evictLRU() {
|
|
3256
|
+
let oldestId = null;
|
|
3257
|
+
let oldestAccess = Infinity;
|
|
3258
|
+
for (const [id, entry] of this.cache) {
|
|
3259
|
+
if (entry.lastAccessedAt < oldestAccess) {
|
|
3260
|
+
oldestAccess = entry.lastAccessedAt;
|
|
3261
|
+
oldestId = id;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
if (oldestId) {
|
|
3265
|
+
this.cache.delete(oldestId);
|
|
3266
|
+
this.stats.totalEvictions++;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
// src/trendAlertEngine.ts
|
|
3272
|
+
var DEFAULT_THRESHOLDS2 = [
|
|
3273
|
+
{
|
|
3274
|
+
alert_type: "BLOCK_RATE_SURGE",
|
|
3275
|
+
metric_name: "block_rate",
|
|
3276
|
+
baseline_window_hours: 24,
|
|
3277
|
+
surge_multiplier: 2,
|
|
3278
|
+
absolute_threshold: 0.5,
|
|
3279
|
+
severity: "CRITICAL",
|
|
3280
|
+
protective_actions: ["TIGHTEN_THRESHOLD", "NOTIFY_ONLY"],
|
|
3281
|
+
cooldown_seconds: 300,
|
|
3282
|
+
enabled: true
|
|
3283
|
+
},
|
|
3284
|
+
{
|
|
3285
|
+
alert_type: "OVERRIDE_RATE_SURGE",
|
|
3286
|
+
metric_name: "override_rate",
|
|
3287
|
+
baseline_window_hours: 24,
|
|
3288
|
+
surge_multiplier: 3,
|
|
3289
|
+
absolute_threshold: 0.3,
|
|
3290
|
+
severity: "CRITICAL",
|
|
3291
|
+
protective_actions: ["TIGHTEN_THRESHOLD", "FORCE_HUMAN_GATE"],
|
|
3292
|
+
cooldown_seconds: 300,
|
|
3293
|
+
enabled: true
|
|
3294
|
+
},
|
|
3295
|
+
{
|
|
3296
|
+
alert_type: "UNKNOWN_REASON_CODE",
|
|
3297
|
+
metric_name: "unknown_reason_rate",
|
|
3298
|
+
baseline_window_hours: 1,
|
|
3299
|
+
surge_multiplier: 1.5,
|
|
3300
|
+
absolute_threshold: 0.1,
|
|
3301
|
+
severity: "WARN",
|
|
3302
|
+
protective_actions: ["NOTIFY_ONLY"],
|
|
3303
|
+
cooldown_seconds: 600,
|
|
3304
|
+
enabled: true
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
alert_type: "APPROVAL_QUEUE_BACKLOG",
|
|
3308
|
+
metric_name: "pending_approval_count",
|
|
3309
|
+
baseline_window_hours: 1,
|
|
3310
|
+
surge_multiplier: 5,
|
|
3311
|
+
absolute_threshold: 50,
|
|
3312
|
+
severity: "CRITICAL",
|
|
3313
|
+
protective_actions: ["DEGRADE", "NOTIFY_ONLY"],
|
|
3314
|
+
cooldown_seconds: 180,
|
|
3315
|
+
enabled: true
|
|
3316
|
+
},
|
|
3317
|
+
{
|
|
3318
|
+
alert_type: "CONFIDENCE_DROP",
|
|
3319
|
+
metric_name: "avg_confidence",
|
|
3320
|
+
baseline_window_hours: 24,
|
|
3321
|
+
surge_multiplier: 0.5,
|
|
3322
|
+
// 低于基线的比例
|
|
3323
|
+
absolute_threshold: 0.3,
|
|
3324
|
+
severity: "WARN",
|
|
3325
|
+
protective_actions: ["TIGHTEN_THRESHOLD"],
|
|
3326
|
+
cooldown_seconds: 600,
|
|
3327
|
+
enabled: true
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
alert_type: "ERROR_RATE_SURGE",
|
|
3331
|
+
metric_name: "error_rate",
|
|
3332
|
+
baseline_window_hours: 1,
|
|
3333
|
+
surge_multiplier: 3,
|
|
3334
|
+
absolute_threshold: 0.2,
|
|
3335
|
+
severity: "EMERGENCY",
|
|
3336
|
+
protective_actions: ["KILL_SWITCH"],
|
|
3337
|
+
cooldown_seconds: 60,
|
|
3338
|
+
enabled: true
|
|
3339
|
+
}
|
|
3340
|
+
];
|
|
3341
|
+
var TrendAlertEngine = class {
|
|
3342
|
+
thresholds;
|
|
3343
|
+
alertHistory = [];
|
|
3344
|
+
lastAlertTime = /* @__PURE__ */ new Map();
|
|
3345
|
+
systemState;
|
|
3346
|
+
metricsBuffer = [];
|
|
3347
|
+
constructor(customThresholds) {
|
|
3348
|
+
this.thresholds = customThresholds || DEFAULT_THRESHOLDS2;
|
|
3349
|
+
this.systemState = {
|
|
3350
|
+
kill_switch: { enabled: false, reason: null, enabled_at: null, enabled_by: "system", auto_triggered: false },
|
|
3351
|
+
degrade: { active: false, level: "LIGHT", reason: "", activated_at: null, auto_recover_at: null },
|
|
3352
|
+
tightened_threshold_multiplier: 1,
|
|
3353
|
+
force_human_gate: false
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
// ---- 核心: 评估趋势 ----
|
|
3357
|
+
evaluate(metrics, tenantId = "default") {
|
|
3358
|
+
const alerts = [];
|
|
3359
|
+
this.metricsBuffer = metrics;
|
|
3360
|
+
for (const threshold of this.thresholds) {
|
|
3361
|
+
if (!threshold.enabled) continue;
|
|
3362
|
+
const metric = metrics.find((m) => m.metric_name === threshold.metric_name);
|
|
3363
|
+
if (!metric) continue;
|
|
3364
|
+
const lastAlert = this.lastAlertTime.get(threshold.alert_type);
|
|
3365
|
+
if (lastAlert && Date.now() - lastAlert < threshold.cooldown_seconds * 1e3) {
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
const triggered = this.checkThreshold(metric, threshold);
|
|
3369
|
+
if (!triggered) continue;
|
|
3370
|
+
const surgeRatio = metric.baseline_value > 0 ? metric.current_value / metric.baseline_value : metric.current_value;
|
|
3371
|
+
const alert = {
|
|
3372
|
+
alert_id: `alert_${threshold.alert_type}_${Date.now()}`,
|
|
3373
|
+
alert_type: threshold.alert_type,
|
|
3374
|
+
severity: threshold.severity,
|
|
3375
|
+
metric_name: threshold.metric_name,
|
|
3376
|
+
current_value: metric.current_value,
|
|
3377
|
+
baseline_value: metric.baseline_value,
|
|
3378
|
+
threshold_value: threshold.absolute_threshold,
|
|
3379
|
+
surge_ratio: Math.round(surgeRatio * 100) / 100,
|
|
3380
|
+
protective_actions_taken: [],
|
|
3381
|
+
message: this.formatAlertMessage(threshold, metric, surgeRatio),
|
|
3382
|
+
tenant_id: tenantId,
|
|
3383
|
+
created_at: Date.now(),
|
|
3384
|
+
acknowledged: false,
|
|
3385
|
+
acknowledged_by: null,
|
|
3386
|
+
acknowledged_at: null
|
|
3387
|
+
};
|
|
3388
|
+
for (const action of threshold.protective_actions) {
|
|
3389
|
+
this.executeProtectiveAction(action, alert);
|
|
3390
|
+
alert.protective_actions_taken.push(action);
|
|
3391
|
+
}
|
|
3392
|
+
alerts.push(alert);
|
|
3393
|
+
this.alertHistory.push(alert);
|
|
3394
|
+
this.lastAlertTime.set(threshold.alert_type, Date.now());
|
|
3395
|
+
}
|
|
3396
|
+
return alerts;
|
|
3397
|
+
}
|
|
3398
|
+
// ---- Cron 评估入口 (DegradeEvaluator) ----
|
|
3399
|
+
cronEvaluate(getMetrics, tenantId = "default") {
|
|
3400
|
+
const metrics = getMetrics();
|
|
3401
|
+
const alerts = this.evaluate(metrics, tenantId);
|
|
3402
|
+
this.checkAutoRecover();
|
|
3403
|
+
return { alerts, state: this.getSystemState() };
|
|
3404
|
+
}
|
|
3405
|
+
// ---- 保护动作执行 ----
|
|
3406
|
+
executeProtectiveAction(action, alert) {
|
|
3407
|
+
switch (action) {
|
|
3408
|
+
case "DEGRADE":
|
|
3409
|
+
this.activateDegrade(alert);
|
|
3410
|
+
break;
|
|
3411
|
+
case "TIGHTEN_THRESHOLD":
|
|
3412
|
+
this.tightenThreshold(alert);
|
|
3413
|
+
break;
|
|
3414
|
+
case "KILL_SWITCH":
|
|
3415
|
+
this.activateKillSwitch(alert);
|
|
3416
|
+
break;
|
|
3417
|
+
case "FORCE_HUMAN_GATE":
|
|
3418
|
+
this.systemState.force_human_gate = true;
|
|
3419
|
+
break;
|
|
3420
|
+
case "NOTIFY_ONLY":
|
|
3421
|
+
break;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
activateDegrade(alert) {
|
|
3425
|
+
const level = alert.severity === "EMERGENCY" ? "SEVERE" : alert.severity === "CRITICAL" ? "MODERATE" : "LIGHT";
|
|
3426
|
+
this.systemState.degrade = {
|
|
3427
|
+
active: true,
|
|
3428
|
+
level,
|
|
3429
|
+
reason: `Auto-degrade triggered by ${alert.alert_type}: ${alert.message}`,
|
|
3430
|
+
activated_at: Date.now(),
|
|
3431
|
+
auto_recover_at: Date.now() + 30 * 60 * 1e3
|
|
3432
|
+
// 30分钟后自动恢复
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
tightenThreshold(alert) {
|
|
3436
|
+
this.systemState.tightened_threshold_multiplier = Math.min(
|
|
3437
|
+
this.systemState.tightened_threshold_multiplier * 1.2,
|
|
3438
|
+
2
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
activateKillSwitch(alert) {
|
|
3442
|
+
this.systemState.kill_switch = {
|
|
3443
|
+
enabled: true,
|
|
3444
|
+
reason: `Auto Kill-Switch triggered by ${alert.alert_type}: ${alert.message}`,
|
|
3445
|
+
enabled_at: Date.now(),
|
|
3446
|
+
enabled_by: "trend_alert_engine",
|
|
3447
|
+
auto_triggered: true
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
// ---- 阈值检查 ----
|
|
3451
|
+
checkThreshold(metric, threshold) {
|
|
3452
|
+
if (threshold.alert_type === "CONFIDENCE_DROP") {
|
|
3453
|
+
return metric.current_value < threshold.absolute_threshold || metric.baseline_value > 0 && metric.current_value < metric.baseline_value * threshold.surge_multiplier;
|
|
3454
|
+
}
|
|
3455
|
+
if (threshold.alert_type === "APPROVAL_QUEUE_BACKLOG") {
|
|
3456
|
+
return metric.current_value > threshold.absolute_threshold;
|
|
3457
|
+
}
|
|
3458
|
+
const surgeTriggered = metric.baseline_value > 0 && metric.current_value > metric.baseline_value * threshold.surge_multiplier;
|
|
3459
|
+
const absoluteTriggered = metric.current_value > threshold.absolute_threshold;
|
|
3460
|
+
return surgeTriggered || absoluteTriggered;
|
|
3461
|
+
}
|
|
3462
|
+
// ---- 自动恢复 ----
|
|
3463
|
+
checkAutoRecover() {
|
|
3464
|
+
if (this.systemState.degrade.active && this.systemState.degrade.auto_recover_at) {
|
|
3465
|
+
if (Date.now() >= this.systemState.degrade.auto_recover_at) {
|
|
3466
|
+
this.systemState.degrade = {
|
|
3467
|
+
active: false,
|
|
3468
|
+
level: "LIGHT",
|
|
3469
|
+
reason: "Auto-recovered after cooldown",
|
|
3470
|
+
activated_at: null,
|
|
3471
|
+
auto_recover_at: null
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
// ---- 手动操作 ----
|
|
3477
|
+
acknowledgeAlert(alertId, acknowledgedBy) {
|
|
3478
|
+
const alert = this.alertHistory.find((a) => a.alert_id === alertId);
|
|
3479
|
+
if (!alert) return false;
|
|
3480
|
+
alert.acknowledged = true;
|
|
3481
|
+
alert.acknowledged_by = acknowledgedBy;
|
|
3482
|
+
alert.acknowledged_at = Date.now();
|
|
3483
|
+
return true;
|
|
3484
|
+
}
|
|
3485
|
+
disableKillSwitch(reason, actor) {
|
|
3486
|
+
if (!this.systemState.kill_switch.enabled) return false;
|
|
3487
|
+
this.systemState.kill_switch = {
|
|
3488
|
+
enabled: false,
|
|
3489
|
+
reason: `Disabled by ${actor}: ${reason}`,
|
|
3490
|
+
enabled_at: null,
|
|
3491
|
+
enabled_by: actor,
|
|
3492
|
+
auto_triggered: false
|
|
3493
|
+
};
|
|
3494
|
+
return true;
|
|
3495
|
+
}
|
|
3496
|
+
resetDegrade() {
|
|
3497
|
+
this.systemState.degrade = {
|
|
3498
|
+
active: false,
|
|
3499
|
+
level: "LIGHT",
|
|
3500
|
+
reason: "Manual reset",
|
|
3501
|
+
activated_at: null,
|
|
3502
|
+
auto_recover_at: null
|
|
3503
|
+
};
|
|
3504
|
+
this.systemState.tightened_threshold_multiplier = 1;
|
|
3505
|
+
this.systemState.force_human_gate = false;
|
|
3506
|
+
}
|
|
3507
|
+
// ---- 查询 ----
|
|
3508
|
+
getSystemState() {
|
|
3509
|
+
return { ...this.systemState };
|
|
3510
|
+
}
|
|
3511
|
+
getAlertHistory(limit = 50) {
|
|
3512
|
+
return this.alertHistory.slice(-limit);
|
|
3513
|
+
}
|
|
3514
|
+
getActiveAlerts() {
|
|
3515
|
+
return this.alertHistory.filter((a) => !a.acknowledged);
|
|
3516
|
+
}
|
|
3517
|
+
getMetricsBuffer() {
|
|
3518
|
+
return [...this.metricsBuffer];
|
|
3519
|
+
}
|
|
3520
|
+
// ---- 辅助 ----
|
|
3521
|
+
formatAlertMessage(threshold, metric, surgeRatio) {
|
|
3522
|
+
return `[${threshold.severity}] ${threshold.alert_type}: ${metric.metric_name} = ${metric.current_value.toFixed(3)} (baseline: ${metric.baseline_value.toFixed(3)}, surge: ${surgeRatio.toFixed(1)}x, threshold: ${threshold.absolute_threshold})`;
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
|
|
3526
|
+
// src/selfConstraintEngine.ts
|
|
3527
|
+
var DEFAULT_THRESHOLDS3 = {
|
|
3528
|
+
risk_density: 0.75,
|
|
3529
|
+
conflict_rate: 0.6,
|
|
3530
|
+
confidence_drop: 0.2,
|
|
3531
|
+
error_rate: 0.15,
|
|
3532
|
+
latency_p99_ms: 5e3,
|
|
3533
|
+
consecutive_failures: 5
|
|
3534
|
+
};
|
|
3535
|
+
var NORMAL_FLAGS = {
|
|
3536
|
+
enable_advanced_features: true,
|
|
3537
|
+
enable_auto_approve: true,
|
|
3538
|
+
enable_batch_operations: true,
|
|
3539
|
+
enable_external_api_calls: true,
|
|
3540
|
+
enable_write_operations: true,
|
|
3541
|
+
require_human_gate: false,
|
|
3542
|
+
require_dual_review: false,
|
|
3543
|
+
max_concurrent_decisions: 100,
|
|
3544
|
+
decision_delay_ms: 0
|
|
3545
|
+
};
|
|
3546
|
+
var CAUTIOUS_FLAGS = {
|
|
3547
|
+
enable_advanced_features: true,
|
|
3548
|
+
enable_auto_approve: false,
|
|
3549
|
+
// 关闭自动审批
|
|
3550
|
+
enable_batch_operations: true,
|
|
3551
|
+
enable_external_api_calls: true,
|
|
3552
|
+
enable_write_operations: true,
|
|
3553
|
+
require_human_gate: false,
|
|
3554
|
+
require_dual_review: false,
|
|
3555
|
+
max_concurrent_decisions: 50,
|
|
3556
|
+
decision_delay_ms: 500
|
|
3557
|
+
// 500ms 延迟
|
|
3558
|
+
};
|
|
3559
|
+
var RESTRICTED_FLAGS = {
|
|
3560
|
+
enable_advanced_features: false,
|
|
3561
|
+
// 关闭高级功能
|
|
3562
|
+
enable_auto_approve: false,
|
|
3563
|
+
enable_batch_operations: false,
|
|
3564
|
+
// 关闭批量操作
|
|
3565
|
+
enable_external_api_calls: true,
|
|
3566
|
+
enable_write_operations: true,
|
|
3567
|
+
require_human_gate: true,
|
|
3568
|
+
// 强制人工门禁
|
|
3569
|
+
require_dual_review: false,
|
|
3570
|
+
max_concurrent_decisions: 10,
|
|
3571
|
+
decision_delay_ms: 2e3
|
|
3572
|
+
// 2s 延迟
|
|
3573
|
+
};
|
|
3574
|
+
var LOCKDOWN_FLAGS = {
|
|
3575
|
+
enable_advanced_features: false,
|
|
3576
|
+
enable_auto_approve: false,
|
|
3577
|
+
enable_batch_operations: false,
|
|
3578
|
+
enable_external_api_calls: false,
|
|
3579
|
+
// 关闭外部调用
|
|
3580
|
+
enable_write_operations: false,
|
|
3581
|
+
// 关闭写操作
|
|
3582
|
+
require_human_gate: true,
|
|
3583
|
+
require_dual_review: true,
|
|
3584
|
+
// 双人审核
|
|
3585
|
+
max_concurrent_decisions: 1,
|
|
3586
|
+
decision_delay_ms: 5e3
|
|
3587
|
+
// 5s 延迟
|
|
3588
|
+
};
|
|
3589
|
+
var LEVEL_FLAGS_MAP = {
|
|
3590
|
+
NORMAL: NORMAL_FLAGS,
|
|
3591
|
+
CAUTIOUS: CAUTIOUS_FLAGS,
|
|
3592
|
+
RESTRICTED: RESTRICTED_FLAGS,
|
|
3593
|
+
LOCKDOWN: LOCKDOWN_FLAGS
|
|
3594
|
+
};
|
|
3595
|
+
var SelfConstraintEngine = class {
|
|
3596
|
+
config;
|
|
3597
|
+
state;
|
|
3598
|
+
constructor(config) {
|
|
3599
|
+
this.config = {
|
|
3600
|
+
thresholds: config?.thresholds || DEFAULT_THRESHOLDS3,
|
|
3601
|
+
flags: config?.flags || NORMAL_FLAGS,
|
|
3602
|
+
auto_recover_after_minutes: config?.auto_recover_after_minutes || 30,
|
|
3603
|
+
evaluation_interval_seconds: config?.evaluation_interval_seconds || 60
|
|
3604
|
+
};
|
|
3605
|
+
this.state = {
|
|
3606
|
+
current_level: "NORMAL",
|
|
3607
|
+
active_modes: [],
|
|
3608
|
+
flags: { ...NORMAL_FLAGS },
|
|
3609
|
+
activated_at: null,
|
|
3610
|
+
auto_recover_at: null,
|
|
3611
|
+
last_evaluation_at: 0,
|
|
3612
|
+
evaluation_count: 0,
|
|
3613
|
+
constraint_history: []
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
// ---- 核心: 评估自我约束 ----
|
|
3617
|
+
evaluate(metrics) {
|
|
3618
|
+
const triggers = [];
|
|
3619
|
+
const modes = /* @__PURE__ */ new Set();
|
|
3620
|
+
triggers.push({
|
|
3621
|
+
metric_name: "risk_density",
|
|
3622
|
+
current_value: metrics.risk_density,
|
|
3623
|
+
threshold_value: this.config.thresholds.risk_density,
|
|
3624
|
+
exceeded: metrics.risk_density >= this.config.thresholds.risk_density
|
|
3625
|
+
});
|
|
3626
|
+
if (metrics.risk_density >= this.config.thresholds.risk_density) {
|
|
3627
|
+
modes.add("CAPABILITY_DOWN");
|
|
3628
|
+
modes.add("PERMISSION_DOWN");
|
|
3629
|
+
}
|
|
3630
|
+
triggers.push({
|
|
3631
|
+
metric_name: "conflict_rate",
|
|
3632
|
+
current_value: metrics.conflict_rate,
|
|
3633
|
+
threshold_value: this.config.thresholds.conflict_rate,
|
|
3634
|
+
exceeded: metrics.conflict_rate >= this.config.thresholds.conflict_rate
|
|
3635
|
+
});
|
|
3636
|
+
if (metrics.conflict_rate >= this.config.thresholds.conflict_rate) {
|
|
3637
|
+
modes.add("SPEED_DOWN");
|
|
3638
|
+
modes.add("PERMISSION_DOWN");
|
|
3639
|
+
}
|
|
3640
|
+
const confidenceDrop = metrics.prev_avg_confidence > 0 ? (metrics.prev_avg_confidence - metrics.avg_confidence) / metrics.prev_avg_confidence : 0;
|
|
3641
|
+
triggers.push({
|
|
3642
|
+
metric_name: "confidence_drop",
|
|
3643
|
+
current_value: confidenceDrop,
|
|
3644
|
+
threshold_value: this.config.thresholds.confidence_drop,
|
|
3645
|
+
exceeded: confidenceDrop >= this.config.thresholds.confidence_drop
|
|
3646
|
+
});
|
|
3647
|
+
if (confidenceDrop >= this.config.thresholds.confidence_drop) {
|
|
3648
|
+
modes.add("SPEED_DOWN");
|
|
3649
|
+
}
|
|
3650
|
+
triggers.push({
|
|
3651
|
+
metric_name: "error_rate",
|
|
3652
|
+
current_value: metrics.error_rate,
|
|
3653
|
+
threshold_value: this.config.thresholds.error_rate,
|
|
3654
|
+
exceeded: metrics.error_rate >= this.config.thresholds.error_rate
|
|
3655
|
+
});
|
|
3656
|
+
if (metrics.error_rate >= this.config.thresholds.error_rate) {
|
|
3657
|
+
modes.add("CAPABILITY_DOWN");
|
|
3658
|
+
}
|
|
3659
|
+
triggers.push({
|
|
3660
|
+
metric_name: "latency_p99_ms",
|
|
3661
|
+
current_value: metrics.latency_p99_ms,
|
|
3662
|
+
threshold_value: this.config.thresholds.latency_p99_ms,
|
|
3663
|
+
exceeded: metrics.latency_p99_ms >= this.config.thresholds.latency_p99_ms
|
|
3664
|
+
});
|
|
3665
|
+
if (metrics.latency_p99_ms >= this.config.thresholds.latency_p99_ms) {
|
|
3666
|
+
modes.add("SPEED_DOWN");
|
|
3667
|
+
}
|
|
3668
|
+
triggers.push({
|
|
3669
|
+
metric_name: "consecutive_failures",
|
|
3670
|
+
current_value: metrics.consecutive_failures,
|
|
3671
|
+
threshold_value: this.config.thresholds.consecutive_failures,
|
|
3672
|
+
exceeded: metrics.consecutive_failures >= this.config.thresholds.consecutive_failures
|
|
3673
|
+
});
|
|
3674
|
+
if (metrics.consecutive_failures >= this.config.thresholds.consecutive_failures) {
|
|
3675
|
+
modes.add("CAPABILITY_DOWN");
|
|
3676
|
+
modes.add("PERMISSION_DOWN");
|
|
3677
|
+
}
|
|
3678
|
+
const triggeredCount = triggers.filter((t) => t.exceeded).length;
|
|
3679
|
+
const level = this.calculateLevel(triggeredCount, metrics.system_pressure);
|
|
3680
|
+
const activeModes = Array.from(modes);
|
|
3681
|
+
const flagsApplied = LEVEL_FLAGS_MAP[level];
|
|
3682
|
+
this.applyConstraint(level, activeModes, flagsApplied);
|
|
3683
|
+
const decision = {
|
|
3684
|
+
decision_id: `sc_${Date.now()}_${this.state.evaluation_count}`,
|
|
3685
|
+
timestamp: Date.now(),
|
|
3686
|
+
level,
|
|
3687
|
+
modes_activated: activeModes,
|
|
3688
|
+
triggers,
|
|
3689
|
+
flags_applied: flagsApplied,
|
|
3690
|
+
reason_code: activeModes.length > 0 ? "SELF_CONSTRAINT_ACTIVATED" : "NORMAL",
|
|
3691
|
+
auto_recover_at: level !== "NORMAL" ? Date.now() + this.config.auto_recover_after_minutes * 60 * 1e3 : null
|
|
3692
|
+
};
|
|
3693
|
+
this.state.constraint_history.push(decision);
|
|
3694
|
+
if (this.state.constraint_history.length > 100) {
|
|
3695
|
+
this.state.constraint_history = this.state.constraint_history.slice(-100);
|
|
3696
|
+
}
|
|
3697
|
+
this.state.evaluation_count++;
|
|
3698
|
+
this.state.last_evaluation_at = Date.now();
|
|
3699
|
+
return decision;
|
|
3700
|
+
}
|
|
3701
|
+
// ---- 约束级别计算 ----
|
|
3702
|
+
calculateLevel(triggeredCount, systemPressure) {
|
|
3703
|
+
const score = triggeredCount + systemPressure * 3;
|
|
3704
|
+
if (score >= 5) return "LOCKDOWN";
|
|
3705
|
+
if (score >= 3) return "RESTRICTED";
|
|
3706
|
+
if (score >= 1) return "CAUTIOUS";
|
|
3707
|
+
return "NORMAL";
|
|
3708
|
+
}
|
|
3709
|
+
// ---- 应用约束 ----
|
|
3710
|
+
applyConstraint(level, modes, flags) {
|
|
3711
|
+
const wasNormal = this.state.current_level === "NORMAL";
|
|
3712
|
+
this.state.current_level = level;
|
|
3713
|
+
this.state.active_modes = modes;
|
|
3714
|
+
this.state.flags = { ...flags };
|
|
3715
|
+
if (level !== "NORMAL" && wasNormal) {
|
|
3716
|
+
this.state.activated_at = Date.now();
|
|
3717
|
+
this.state.auto_recover_at = Date.now() + this.config.auto_recover_after_minutes * 60 * 1e3;
|
|
3718
|
+
} else if (level === "NORMAL") {
|
|
3719
|
+
this.state.activated_at = null;
|
|
3720
|
+
this.state.auto_recover_at = null;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
// ---- 自动恢复检查 ----
|
|
3724
|
+
checkAutoRecover() {
|
|
3725
|
+
if (this.state.current_level === "NORMAL") return false;
|
|
3726
|
+
if (!this.state.auto_recover_at) return false;
|
|
3727
|
+
if (Date.now() >= this.state.auto_recover_at) {
|
|
3728
|
+
this.reset();
|
|
3729
|
+
return true;
|
|
3730
|
+
}
|
|
3731
|
+
return false;
|
|
3732
|
+
}
|
|
3733
|
+
// ---- 手动操作 ----
|
|
3734
|
+
reset() {
|
|
3735
|
+
this.state.current_level = "NORMAL";
|
|
3736
|
+
this.state.active_modes = [];
|
|
3737
|
+
this.state.flags = { ...NORMAL_FLAGS };
|
|
3738
|
+
this.state.activated_at = null;
|
|
3739
|
+
this.state.auto_recover_at = null;
|
|
3740
|
+
}
|
|
3741
|
+
forceLevel(level) {
|
|
3742
|
+
const flags = LEVEL_FLAGS_MAP[level];
|
|
3743
|
+
this.applyConstraint(level, level === "NORMAL" ? [] : ["CAPABILITY_DOWN", "PERMISSION_DOWN"], flags);
|
|
3744
|
+
}
|
|
3745
|
+
// ---- 决策前检查 (Gate 集成) ----
|
|
3746
|
+
preDecisionCheck(action, resource) {
|
|
3747
|
+
const flags = this.state.flags;
|
|
3748
|
+
if (!flags.enable_write_operations && this.isWriteAction(action)) {
|
|
3749
|
+
return {
|
|
3750
|
+
allowed: false,
|
|
3751
|
+
delay_ms: 0,
|
|
3752
|
+
requires_human: true,
|
|
3753
|
+
requires_dual_review: flags.require_dual_review,
|
|
3754
|
+
reason: `Write operations disabled at ${this.state.current_level} level`
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
if (!flags.enable_external_api_calls && this.isExternalAction(action)) {
|
|
3758
|
+
return {
|
|
3759
|
+
allowed: false,
|
|
3760
|
+
delay_ms: 0,
|
|
3761
|
+
requires_human: true,
|
|
3762
|
+
requires_dual_review: flags.require_dual_review,
|
|
3763
|
+
reason: `External API calls disabled at ${this.state.current_level} level`
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
if (!flags.enable_batch_operations && this.isBatchAction(action)) {
|
|
3767
|
+
return {
|
|
3768
|
+
allowed: false,
|
|
3769
|
+
delay_ms: 0,
|
|
3770
|
+
requires_human: true,
|
|
3771
|
+
requires_dual_review: false,
|
|
3772
|
+
reason: `Batch operations disabled at ${this.state.current_level} level`
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
return {
|
|
3776
|
+
allowed: true,
|
|
3777
|
+
delay_ms: flags.decision_delay_ms,
|
|
3778
|
+
requires_human: flags.require_human_gate,
|
|
3779
|
+
requires_dual_review: flags.require_dual_review,
|
|
3780
|
+
reason: this.state.current_level === "NORMAL" ? "OK" : `Constrained at ${this.state.current_level} level`
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
// ---- 查询 ----
|
|
3784
|
+
getState() {
|
|
3785
|
+
return {
|
|
3786
|
+
...this.state,
|
|
3787
|
+
flags: { ...this.state.flags },
|
|
3788
|
+
active_modes: [...this.state.active_modes],
|
|
3789
|
+
constraint_history: [...this.state.constraint_history]
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
getCurrentLevel() {
|
|
3793
|
+
return this.state.current_level;
|
|
3794
|
+
}
|
|
3795
|
+
getFlags() {
|
|
3796
|
+
return { ...this.state.flags };
|
|
3797
|
+
}
|
|
3798
|
+
isConstrained() {
|
|
3799
|
+
return this.state.current_level !== "NORMAL";
|
|
3800
|
+
}
|
|
3801
|
+
// ---- 辅助 ----
|
|
3802
|
+
isWriteAction(action) {
|
|
3803
|
+
const writePatterns = ["write", "create", "update", "delete", "insert", "modify", "patch", "put"];
|
|
3804
|
+
return writePatterns.some((p) => action.toLowerCase().includes(p));
|
|
3805
|
+
}
|
|
3806
|
+
isExternalAction(action) {
|
|
3807
|
+
const externalPatterns = ["external", "api_call", "webhook", "http", "fetch", "request"];
|
|
3808
|
+
return externalPatterns.some((p) => action.toLowerCase().includes(p));
|
|
3809
|
+
}
|
|
3810
|
+
isBatchAction(action) {
|
|
3811
|
+
const batchPatterns = ["batch", "bulk", "mass", "multi"];
|
|
3812
|
+
return batchPatterns.some((p) => action.toLowerCase().includes(p));
|
|
3813
|
+
}
|
|
3814
|
+
};
|
|
3815
|
+
|
|
3816
|
+
// src/contributionSettlement.ts
|
|
3817
|
+
var DEFAULT_SCORE_CONFIG = {
|
|
3818
|
+
version: "1.0.0",
|
|
3819
|
+
weight_risk: 0.35,
|
|
3820
|
+
weight_cost: 0.25,
|
|
3821
|
+
weight_human: 0.2,
|
|
3822
|
+
weight_stability: 0.2,
|
|
3823
|
+
weight_penalty: 1,
|
|
3824
|
+
caps: {
|
|
3825
|
+
max_single_event: 100,
|
|
3826
|
+
max_risk_positive_when_risk_up: false
|
|
3827
|
+
},
|
|
3828
|
+
guardrails: [
|
|
3829
|
+
"RISK_UP_BLOCKS_POSITIVE",
|
|
3830
|
+
"EVIDENCE_DOWNGRADE_NEGATIVE",
|
|
3831
|
+
"OVERRIDE_INCREASE_PENALTY"
|
|
3832
|
+
],
|
|
3833
|
+
approved_by: "system",
|
|
3834
|
+
approved_at: Date.now()
|
|
3835
|
+
};
|
|
3836
|
+
var MVP_ATTRIBUTION_RULES = [
|
|
3837
|
+
{
|
|
3838
|
+
condition: "RULE_TRIGGERED_BLOCK_WARN",
|
|
3839
|
+
target_entity_type: "rule",
|
|
3840
|
+
target_entity_field: "rule_id",
|
|
3841
|
+
weight: 1
|
|
3842
|
+
},
|
|
3843
|
+
{
|
|
3844
|
+
condition: "EVIDENCE_UPGRADED_ALLOW",
|
|
3845
|
+
target_entity_type: "tool",
|
|
3846
|
+
target_entity_field: "evidence_source",
|
|
3847
|
+
weight: 0.8
|
|
3848
|
+
},
|
|
3849
|
+
{
|
|
3850
|
+
condition: "MODEL_ROUTE_CHEAPER_SAME_RESULT",
|
|
3851
|
+
target_entity_type: "model",
|
|
3852
|
+
target_entity_field: "modelops_route_id",
|
|
3853
|
+
weight: 0.7
|
|
3854
|
+
},
|
|
3855
|
+
{
|
|
3856
|
+
condition: "CACHE_HIT_SKIP_TOOL",
|
|
3857
|
+
target_entity_type: "system",
|
|
3858
|
+
target_entity_field: "context_accelerator",
|
|
3859
|
+
weight: 0.6
|
|
3860
|
+
},
|
|
3861
|
+
{
|
|
3862
|
+
condition: "HUMAN_OVERRIDE_CORRECTION",
|
|
3863
|
+
target_entity_type: "human",
|
|
3864
|
+
target_entity_field: "actor_id",
|
|
3865
|
+
weight: 0.5
|
|
3866
|
+
}
|
|
3867
|
+
];
|
|
3868
|
+
var BUILTIN_PENALTY_CHECKS = [
|
|
3869
|
+
{
|
|
3870
|
+
name: "OVERRIDE_INCREASE",
|
|
3871
|
+
detect: (_events, ctx) => {
|
|
3872
|
+
if (ctx.override_count_delta > 0) {
|
|
3873
|
+
return ctx.override_count_delta * 5;
|
|
3874
|
+
}
|
|
3875
|
+
return 0;
|
|
3876
|
+
}
|
|
3877
|
+
},
|
|
3878
|
+
{
|
|
3879
|
+
name: "REPLAY_FAIL_INCREASE",
|
|
3880
|
+
detect: (_events, ctx) => {
|
|
3881
|
+
if (ctx.replay_fail_delta > 0) {
|
|
3882
|
+
return ctx.replay_fail_delta * 10;
|
|
3883
|
+
}
|
|
3884
|
+
return 0;
|
|
3885
|
+
}
|
|
3886
|
+
},
|
|
3887
|
+
{
|
|
3888
|
+
name: "RISK_REGRESSION",
|
|
3889
|
+
detect: (_events, ctx) => {
|
|
3890
|
+
if (ctx.risk_regression) {
|
|
3891
|
+
return 50;
|
|
3892
|
+
}
|
|
3893
|
+
return 0;
|
|
3894
|
+
}
|
|
3895
|
+
},
|
|
3896
|
+
{
|
|
3897
|
+
name: "EVIDENCE_DOWNGRADE",
|
|
3898
|
+
detect: (_events, ctx) => {
|
|
3899
|
+
if (ctx.evidence_downgrade) {
|
|
3900
|
+
return 30;
|
|
3901
|
+
}
|
|
3902
|
+
return 0;
|
|
3903
|
+
}
|
|
3904
|
+
},
|
|
3905
|
+
{
|
|
3906
|
+
name: "SELF_MANUFACTURE_DETECT",
|
|
3907
|
+
detect: (events, _ctx) => {
|
|
3908
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
3909
|
+
for (const e of events) {
|
|
3910
|
+
const key = `${e.entity_type}:${e.entity_id}`;
|
|
3911
|
+
const entry = entityMap.get(key) || { negative: 0, positive: 0 };
|
|
3912
|
+
if (e.delta_value < 0) entry.negative += Math.abs(e.delta_value);
|
|
3913
|
+
else entry.positive += e.delta_value;
|
|
3914
|
+
entityMap.set(key, entry);
|
|
3915
|
+
}
|
|
3916
|
+
let penalty = 0;
|
|
3917
|
+
for (const [, entry] of entityMap) {
|
|
3918
|
+
if (entry.negative > 20 && entry.positive > 20) {
|
|
3919
|
+
const ratio = Math.min(entry.negative, entry.positive) / Math.max(entry.negative, entry.positive);
|
|
3920
|
+
if (ratio > 0.7) {
|
|
3921
|
+
penalty += 25;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
return penalty;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
];
|
|
3929
|
+
var ContributionSettlementEngine = class {
|
|
3930
|
+
config;
|
|
3931
|
+
attributionRules;
|
|
3932
|
+
penaltyChecks;
|
|
3933
|
+
eventStore = [];
|
|
3934
|
+
scoreStore = [];
|
|
3935
|
+
constructor(config, customRules, customPenalties) {
|
|
3936
|
+
this.config = { ...DEFAULT_SCORE_CONFIG, ...config };
|
|
3937
|
+
this.attributionRules = customRules || MVP_ATTRIBUTION_RULES;
|
|
3938
|
+
this.penaltyChecks = [...BUILTIN_PENALTY_CHECKS, ...customPenalties || []];
|
|
3939
|
+
}
|
|
3940
|
+
// ---- 事件记录 ----
|
|
3941
|
+
recordEvent(event) {
|
|
3942
|
+
if (Math.abs(event.delta_value) > this.config.caps.max_single_event) {
|
|
3943
|
+
event.delta_value = event.delta_value > 0 ? this.config.caps.max_single_event : -this.config.caps.max_single_event;
|
|
3944
|
+
}
|
|
3945
|
+
const exists = this.eventStore.find((e) => e.event_id === event.event_id);
|
|
3946
|
+
if (exists) return exists;
|
|
3947
|
+
this.eventStore.push(event);
|
|
3948
|
+
return event;
|
|
3949
|
+
}
|
|
3950
|
+
// ---- 归因 ----
|
|
3951
|
+
attribute(traceId, condition, entityType, entityId) {
|
|
3952
|
+
const rule = this.attributionRules.find((r) => r.condition === condition);
|
|
3953
|
+
if (!rule) return null;
|
|
3954
|
+
const event = {
|
|
3955
|
+
event_id: `attr_${traceId}_${Date.now()}`,
|
|
3956
|
+
trace_id: traceId,
|
|
3957
|
+
decision_id: traceId,
|
|
3958
|
+
tenant_id: "default",
|
|
3959
|
+
entity_type: entityType,
|
|
3960
|
+
entity_id: entityId,
|
|
3961
|
+
dimension: this.inferDimension(condition),
|
|
3962
|
+
delta_value: rule.weight * 10,
|
|
3963
|
+
// 基础分 * 权重
|
|
3964
|
+
delta_confidence: rule.weight,
|
|
3965
|
+
baseline_ref: "current",
|
|
3966
|
+
evidence_bundle_hash: "",
|
|
3967
|
+
created_at: Date.now()
|
|
3968
|
+
};
|
|
3969
|
+
return this.recordEvent(event);
|
|
3970
|
+
}
|
|
3971
|
+
// ---- 窗口结算 ----
|
|
3972
|
+
settle(context) {
|
|
3973
|
+
const windowEvents = this.eventStore.filter(
|
|
3974
|
+
(e) => e.tenant_id === context.tenant_id && e.created_at >= context.window_start && e.created_at <= context.window_end
|
|
3975
|
+
);
|
|
3976
|
+
const entityGroups = /* @__PURE__ */ new Map();
|
|
3977
|
+
for (const e of windowEvents) {
|
|
3978
|
+
const key = `${e.entity_type}:${e.entity_id}`;
|
|
3979
|
+
const group = entityGroups.get(key) || [];
|
|
3980
|
+
group.push(e);
|
|
3981
|
+
entityGroups.set(key, group);
|
|
3982
|
+
}
|
|
3983
|
+
const scores = [];
|
|
3984
|
+
for (const [key, events] of entityGroups) {
|
|
3985
|
+
const [entityType, entityId] = key.split(":");
|
|
3986
|
+
let scoreRisk = 0, scoreCost = 0, scoreHuman = 0, scoreStability = 0;
|
|
3987
|
+
let totalConfidence = 0;
|
|
3988
|
+
for (const e of events) {
|
|
3989
|
+
const val = e.delta_value * e.delta_confidence;
|
|
3990
|
+
switch (e.dimension) {
|
|
3991
|
+
case "RISK":
|
|
3992
|
+
scoreRisk += val;
|
|
3993
|
+
break;
|
|
3994
|
+
case "COST":
|
|
3995
|
+
scoreCost += val;
|
|
3996
|
+
break;
|
|
3997
|
+
case "HUMAN":
|
|
3998
|
+
scoreHuman += val;
|
|
3999
|
+
break;
|
|
4000
|
+
case "STABILITY":
|
|
4001
|
+
scoreStability += val;
|
|
4002
|
+
break;
|
|
4003
|
+
}
|
|
4004
|
+
totalConfidence += e.delta_confidence;
|
|
4005
|
+
}
|
|
4006
|
+
if (!this.config.caps.max_risk_positive_when_risk_up && context.risk_regression) {
|
|
4007
|
+
scoreRisk = Math.min(scoreRisk, 0);
|
|
4008
|
+
scoreCost = Math.min(scoreCost, 0);
|
|
4009
|
+
scoreHuman = Math.min(scoreHuman, 0);
|
|
4010
|
+
scoreStability = Math.min(scoreStability, 0);
|
|
4011
|
+
}
|
|
4012
|
+
let penaltyTotal = 0;
|
|
4013
|
+
for (const check of this.penaltyChecks) {
|
|
4014
|
+
penaltyTotal += check.detect(events, context);
|
|
4015
|
+
}
|
|
4016
|
+
const scoreTotal = this.config.weight_risk * scoreRisk + this.config.weight_cost * scoreCost + this.config.weight_human * scoreHuman + this.config.weight_stability * scoreStability - this.config.weight_penalty * penaltyTotal;
|
|
4017
|
+
const avgConfidence = events.length > 0 ? totalConfidence / events.length : 0;
|
|
4018
|
+
const score = {
|
|
4019
|
+
score_id: `score_${entityType}_${entityId}_${context.window_start}`,
|
|
4020
|
+
window_start: context.window_start,
|
|
4021
|
+
window_end: context.window_end,
|
|
4022
|
+
tenant_id: context.tenant_id,
|
|
4023
|
+
entity_type: entityType,
|
|
4024
|
+
entity_id: entityId,
|
|
4025
|
+
score_risk: Math.round(scoreRisk * 100) / 100,
|
|
4026
|
+
score_cost: Math.round(scoreCost * 100) / 100,
|
|
4027
|
+
score_human: Math.round(scoreHuman * 100) / 100,
|
|
4028
|
+
score_stability: Math.round(scoreStability * 100) / 100,
|
|
4029
|
+
score_total: Math.round(scoreTotal * 100) / 100,
|
|
4030
|
+
penalty_total: Math.round(penaltyTotal * 100) / 100,
|
|
4031
|
+
confidence: Math.round(avgConfidence * 100) / 100,
|
|
4032
|
+
score_config_version: this.config.version,
|
|
4033
|
+
created_at: Date.now()
|
|
4034
|
+
};
|
|
4035
|
+
scores.push(score);
|
|
4036
|
+
this.scoreStore.push(score);
|
|
4037
|
+
}
|
|
4038
|
+
return scores;
|
|
4039
|
+
}
|
|
4040
|
+
// ---- 控制面联动 ----
|
|
4041
|
+
getResourceAllocation(entityType, entityId) {
|
|
4042
|
+
const recentScores = this.scoreStore.filter(
|
|
4043
|
+
(s) => s.entity_type === entityType && s.entity_id === entityId
|
|
4044
|
+
);
|
|
4045
|
+
if (recentScores.length === 0) {
|
|
4046
|
+
return {
|
|
4047
|
+
token_budget_multiplier: 1,
|
|
4048
|
+
concurrency_quota_multiplier: 1,
|
|
4049
|
+
auto_approve_eligible: false,
|
|
4050
|
+
requires_dual_review: false
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
const latestScore = recentScores[recentScores.length - 1];
|
|
4054
|
+
const total = latestScore.score_total;
|
|
4055
|
+
return {
|
|
4056
|
+
token_budget_multiplier: total > 50 ? 1.5 : total > 0 ? 1 : 0.5,
|
|
4057
|
+
concurrency_quota_multiplier: total > 50 ? 1.3 : total > 0 ? 1 : 0.7,
|
|
4058
|
+
auto_approve_eligible: total > 80 && latestScore.penalty_total === 0,
|
|
4059
|
+
requires_dual_review: total < -20 || latestScore.penalty_total > 30
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
// ---- 审计抽查 ----
|
|
4063
|
+
auditSample(entityType, entityId, sampleSize = 3) {
|
|
4064
|
+
const entityEvents = this.eventStore.filter(
|
|
4065
|
+
(e) => e.entity_type === entityType && e.entity_id === entityId
|
|
4066
|
+
);
|
|
4067
|
+
const shuffled = [...entityEvents].sort(() => Math.random() - 0.5);
|
|
4068
|
+
return shuffled.slice(0, Math.min(sampleSize, shuffled.length));
|
|
4069
|
+
}
|
|
4070
|
+
// ---- 辅助 ----
|
|
4071
|
+
inferDimension(condition) {
|
|
4072
|
+
if (condition.includes("RULE") || condition.includes("BLOCK")) return "RISK";
|
|
4073
|
+
if (condition.includes("COST") || condition.includes("CACHE") || condition.includes("CHEAPER")) return "COST";
|
|
4074
|
+
if (condition.includes("HUMAN") || condition.includes("OVERRIDE")) return "HUMAN";
|
|
4075
|
+
return "STABILITY";
|
|
4076
|
+
}
|
|
4077
|
+
getConfig() {
|
|
4078
|
+
return { ...this.config };
|
|
4079
|
+
}
|
|
4080
|
+
getEventCount() {
|
|
4081
|
+
return this.eventStore.length;
|
|
4082
|
+
}
|
|
4083
|
+
getScoreCount() {
|
|
4084
|
+
return this.scoreStore.length;
|
|
4085
|
+
}
|
|
4086
|
+
};
|
|
4087
|
+
|
|
4088
|
+
// src/overrideDriftAnalyzer.ts
|
|
4089
|
+
var DEFAULT_CONFIG6 = {
|
|
4090
|
+
baseline_window_hours: 168,
|
|
4091
|
+
// 7 天基线
|
|
4092
|
+
analysis_window_hours: 24,
|
|
4093
|
+
// 24 小时分析窗口
|
|
4094
|
+
rate_drift_threshold: 0.3,
|
|
4095
|
+
// override 率偏离 30%
|
|
4096
|
+
concentration_threshold: 0.5,
|
|
4097
|
+
// 单一实体占比超 50%
|
|
4098
|
+
risk_drift_threshold: 0.4,
|
|
4099
|
+
// 高风险 override 占比偏离 40%
|
|
4100
|
+
temporal_drift_threshold: 0.3,
|
|
4101
|
+
// 时间分布偏离 30%
|
|
4102
|
+
composite_alert_threshold: 0.5,
|
|
4103
|
+
// 综合漂移指数超 0.5 告警
|
|
4104
|
+
min_sample_size: 10
|
|
4105
|
+
// 最小样本量
|
|
4106
|
+
};
|
|
4107
|
+
var OverrideDriftAnalyzer = class {
|
|
4108
|
+
config;
|
|
4109
|
+
events = [];
|
|
4110
|
+
baselineWindow = null;
|
|
4111
|
+
alerts = [];
|
|
4112
|
+
constructor(config) {
|
|
4113
|
+
this.config = { ...DEFAULT_CONFIG6, ...config };
|
|
4114
|
+
}
|
|
4115
|
+
// ---- 事件记录 ----
|
|
4116
|
+
recordOverride(event) {
|
|
4117
|
+
if (this.events.find((e) => e.event_id === event.event_id)) return;
|
|
4118
|
+
this.events.push(event);
|
|
4119
|
+
}
|
|
4120
|
+
// ---- 窗口统计 ----
|
|
4121
|
+
computeWindow(start, end, totalDecisions) {
|
|
4122
|
+
const windowEvents = this.events.filter(
|
|
4123
|
+
(e) => e.created_at >= start && e.created_at <= end
|
|
4124
|
+
);
|
|
4125
|
+
const byActor = /* @__PURE__ */ new Map();
|
|
4126
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
4127
|
+
const byRiskLevel = { LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 };
|
|
4128
|
+
const byHour = new Array(24).fill(0);
|
|
4129
|
+
for (const e of windowEvents) {
|
|
4130
|
+
byActor.set(e.actor_id, (byActor.get(e.actor_id) || 0) + 1);
|
|
4131
|
+
byRule.set(e.rule_id, (byRule.get(e.rule_id) || 0) + 1);
|
|
4132
|
+
byRiskLevel[e.risk_level] = (byRiskLevel[e.risk_level] || 0) + 1;
|
|
4133
|
+
const hour = new Date(e.created_at).getHours();
|
|
4134
|
+
byHour[hour]++;
|
|
4135
|
+
}
|
|
4136
|
+
const total = totalDecisions || Math.max(windowEvents.length * 5, 1);
|
|
4137
|
+
return {
|
|
4138
|
+
window_start: start,
|
|
4139
|
+
window_end: end,
|
|
4140
|
+
total_decisions: total,
|
|
4141
|
+
total_overrides: windowEvents.length,
|
|
4142
|
+
override_rate: total > 0 ? windowEvents.length / total : 0,
|
|
4143
|
+
by_actor: byActor,
|
|
4144
|
+
by_rule: byRule,
|
|
4145
|
+
by_risk_level: byRiskLevel,
|
|
4146
|
+
by_hour: byHour
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
// ---- 基线计算 ----
|
|
4150
|
+
updateBaseline(totalDecisions) {
|
|
4151
|
+
const now = Date.now();
|
|
4152
|
+
const start = now - this.config.baseline_window_hours * 60 * 60 * 1e3;
|
|
4153
|
+
this.baselineWindow = this.computeWindow(start, now, totalDecisions);
|
|
4154
|
+
return this.baselineWindow;
|
|
4155
|
+
}
|
|
4156
|
+
// ---- 漂移指数计算 ----
|
|
4157
|
+
calculateDriftIndex(currentWindow, totalDecisions) {
|
|
4158
|
+
const now = Date.now();
|
|
4159
|
+
const current = currentWindow || this.computeWindow(
|
|
4160
|
+
now - this.config.analysis_window_hours * 60 * 60 * 1e3,
|
|
4161
|
+
now,
|
|
4162
|
+
totalDecisions
|
|
4163
|
+
);
|
|
4164
|
+
if (!this.baselineWindow) {
|
|
4165
|
+
this.updateBaseline(totalDecisions);
|
|
4166
|
+
}
|
|
4167
|
+
const baseline = this.baselineWindow;
|
|
4168
|
+
const rateDrift = baseline.override_rate > 0 ? Math.abs(current.override_rate - baseline.override_rate) / baseline.override_rate : current.override_rate > 0 ? 1 : 0;
|
|
4169
|
+
const actorConcentration = this.calculateConcentration(current.by_actor, current.total_overrides);
|
|
4170
|
+
const baselineActorConc = this.calculateConcentration(baseline.by_actor, baseline.total_overrides);
|
|
4171
|
+
const concentrationDrift = Math.abs(actorConcentration - baselineActorConc);
|
|
4172
|
+
const currentHighRisk = ((current.by_risk_level["HIGH"] || 0) + (current.by_risk_level["CRITICAL"] || 0)) / Math.max(current.total_overrides, 1);
|
|
4173
|
+
const baselineHighRisk = ((baseline.by_risk_level["HIGH"] || 0) + (baseline.by_risk_level["CRITICAL"] || 0)) / Math.max(baseline.total_overrides, 1);
|
|
4174
|
+
const riskDrift = Math.abs(currentHighRisk - baselineHighRisk);
|
|
4175
|
+
const temporalDrift = this.calculateTemporalDrift(current.by_hour, baseline.by_hour);
|
|
4176
|
+
const composite = rateDrift * 0.35 + concentrationDrift * 0.25 + riskDrift * 0.25 + temporalDrift * 0.15;
|
|
4177
|
+
const trend = this.determineTrend(composite);
|
|
4178
|
+
return {
|
|
4179
|
+
value: Math.min(Math.round(composite * 1e3) / 1e3, 1),
|
|
4180
|
+
rate_drift: Math.round(rateDrift * 1e3) / 1e3,
|
|
4181
|
+
concentration_drift: Math.round(concentrationDrift * 1e3) / 1e3,
|
|
4182
|
+
risk_drift: Math.round(riskDrift * 1e3) / 1e3,
|
|
4183
|
+
temporal_drift: Math.round(temporalDrift * 1e3) / 1e3,
|
|
4184
|
+
trend
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
// ---- 分析 & 告警 ----
|
|
4188
|
+
analyze(totalDecisions) {
|
|
4189
|
+
const driftIndex = this.calculateDriftIndex(void 0, totalDecisions);
|
|
4190
|
+
if (driftIndex.value < this.config.composite_alert_threshold) {
|
|
4191
|
+
return null;
|
|
4192
|
+
}
|
|
4193
|
+
const patterns = this.detectPatterns(driftIndex);
|
|
4194
|
+
const recommendations = this.generateRecommendations(driftIndex, patterns);
|
|
4195
|
+
const severity = driftIndex.value >= 0.8 ? "CRITICAL" : driftIndex.value >= 0.5 ? "WARN" : "INFO";
|
|
4196
|
+
const alert = {
|
|
4197
|
+
alert_id: `da_${Date.now()}`,
|
|
4198
|
+
drift_index: driftIndex,
|
|
4199
|
+
severity,
|
|
4200
|
+
message: `Override drift detected: composite index = ${driftIndex.value} (${driftIndex.trend})`,
|
|
4201
|
+
patterns_detected: patterns,
|
|
4202
|
+
recommendations,
|
|
4203
|
+
created_at: Date.now()
|
|
4204
|
+
};
|
|
4205
|
+
this.alerts.push(alert);
|
|
4206
|
+
return alert;
|
|
4207
|
+
}
|
|
4208
|
+
// ---- 模式检测 ----
|
|
4209
|
+
detectPatterns(driftIndex) {
|
|
4210
|
+
const patterns = [];
|
|
4211
|
+
if (driftIndex.rate_drift > this.config.rate_drift_threshold) {
|
|
4212
|
+
patterns.push({
|
|
4213
|
+
pattern_type: "RATE_SPIKE",
|
|
4214
|
+
description: `Override rate increased by ${(driftIndex.rate_drift * 100).toFixed(1)}% from baseline`,
|
|
4215
|
+
evidence: { rate_drift: driftIndex.rate_drift },
|
|
4216
|
+
confidence: Math.min(driftIndex.rate_drift, 1)
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
4219
|
+
if (driftIndex.concentration_drift > this.config.concentration_threshold) {
|
|
4220
|
+
patterns.push({
|
|
4221
|
+
pattern_type: "ACTOR_CONCENTRATION",
|
|
4222
|
+
description: "Override activity concentrated on fewer actors than baseline",
|
|
4223
|
+
evidence: { concentration_drift: driftIndex.concentration_drift },
|
|
4224
|
+
confidence: Math.min(driftIndex.concentration_drift, 1)
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
if (driftIndex.risk_drift > this.config.risk_drift_threshold) {
|
|
4228
|
+
patterns.push({
|
|
4229
|
+
pattern_type: "RISK_ESCALATION",
|
|
4230
|
+
description: `High-risk override proportion shifted by ${(driftIndex.risk_drift * 100).toFixed(1)}%`,
|
|
4231
|
+
evidence: { risk_drift: driftIndex.risk_drift },
|
|
4232
|
+
confidence: Math.min(driftIndex.risk_drift, 1)
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
if (driftIndex.temporal_drift > this.config.temporal_drift_threshold) {
|
|
4236
|
+
patterns.push({
|
|
4237
|
+
pattern_type: "TIME_ANOMALY",
|
|
4238
|
+
description: "Override time distribution deviates from baseline pattern",
|
|
4239
|
+
evidence: { temporal_drift: driftIndex.temporal_drift },
|
|
4240
|
+
confidence: Math.min(driftIndex.temporal_drift, 1)
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
return patterns;
|
|
4244
|
+
}
|
|
4245
|
+
// ---- 建议生成 ----
|
|
4246
|
+
generateRecommendations(driftIndex, patterns) {
|
|
4247
|
+
const recs = [];
|
|
4248
|
+
if (patterns.some((p) => p.pattern_type === "RATE_SPIKE")) {
|
|
4249
|
+
recs.push("Review recent policy changes that may have increased false positives");
|
|
4250
|
+
recs.push("Check if new rules are overly restrictive, causing excessive overrides");
|
|
4251
|
+
}
|
|
4252
|
+
if (patterns.some((p) => p.pattern_type === "ACTOR_CONCENTRATION")) {
|
|
4253
|
+
recs.push("Investigate concentrated override activity for potential abuse");
|
|
4254
|
+
recs.push("Consider implementing per-actor override quotas");
|
|
4255
|
+
}
|
|
4256
|
+
if (patterns.some((p) => p.pattern_type === "RISK_ESCALATION")) {
|
|
4257
|
+
recs.push("Audit high-risk overrides for potential security implications");
|
|
4258
|
+
recs.push("Consider requiring dual approval for high-risk overrides");
|
|
4259
|
+
}
|
|
4260
|
+
if (patterns.some((p) => p.pattern_type === "TIME_ANOMALY")) {
|
|
4261
|
+
recs.push("Check for automated override scripts running at unusual hours");
|
|
4262
|
+
recs.push("Review access patterns during off-hours");
|
|
4263
|
+
}
|
|
4264
|
+
if (driftIndex.value >= 0.8) {
|
|
4265
|
+
recs.push("CRITICAL: Consider activating self-constraint engine to restrict override capability");
|
|
4266
|
+
}
|
|
4267
|
+
return recs;
|
|
4268
|
+
}
|
|
4269
|
+
// ---- 辅助计算 ----
|
|
4270
|
+
calculateConcentration(distribution, total) {
|
|
4271
|
+
if (total === 0) return 0;
|
|
4272
|
+
let herfindahl = 0;
|
|
4273
|
+
for (const [, count] of distribution) {
|
|
4274
|
+
const share = count / total;
|
|
4275
|
+
herfindahl += share * share;
|
|
4276
|
+
}
|
|
4277
|
+
return herfindahl;
|
|
4278
|
+
}
|
|
4279
|
+
calculateTemporalDrift(current, baseline) {
|
|
4280
|
+
const currentTotal = current.reduce((a, b) => a + b, 0) || 1;
|
|
4281
|
+
const baselineTotal = baseline.reduce((a, b) => a + b, 0) || 1;
|
|
4282
|
+
let divergence = 0;
|
|
4283
|
+
for (let i = 0; i < 24; i++) {
|
|
4284
|
+
const p = current[i] / currentTotal;
|
|
4285
|
+
const q = baseline[i] / baselineTotal || 1 / 24;
|
|
4286
|
+
if (p > 0) {
|
|
4287
|
+
divergence += p * Math.log(p / q);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
return Math.min(divergence / 2, 1);
|
|
4291
|
+
}
|
|
4292
|
+
determineTrend(composite) {
|
|
4293
|
+
const recentAlerts = this.alerts.slice(-5);
|
|
4294
|
+
if (recentAlerts.length < 2) return "STABLE";
|
|
4295
|
+
const values = recentAlerts.map((a) => a.drift_index.value);
|
|
4296
|
+
const diffs = values.slice(1).map((v, i) => v - values[i]);
|
|
4297
|
+
const avgDiff = diffs.reduce((a, b) => a + b, 0) / diffs.length;
|
|
4298
|
+
const variance = diffs.reduce((a, b) => a + (b - avgDiff) ** 2, 0) / diffs.length;
|
|
4299
|
+
if (variance > 0.1) return "VOLATILE";
|
|
4300
|
+
if (avgDiff > 0.05) return "RISING";
|
|
4301
|
+
if (avgDiff < -0.05) return "FALLING";
|
|
4302
|
+
return "STABLE";
|
|
4303
|
+
}
|
|
4304
|
+
// ---- 查询 ----
|
|
4305
|
+
getAlerts(limit = 20) {
|
|
4306
|
+
return this.alerts.slice(-limit);
|
|
4307
|
+
}
|
|
4308
|
+
getEventCount() {
|
|
4309
|
+
return this.events.length;
|
|
4310
|
+
}
|
|
4311
|
+
getLatestDriftIndex(totalDecisions) {
|
|
4312
|
+
return this.calculateDriftIndex(void 0, totalDecisions);
|
|
4313
|
+
}
|
|
4314
|
+
getStats() {
|
|
4315
|
+
const now = Date.now();
|
|
4316
|
+
const currentWindow = this.computeWindow(
|
|
4317
|
+
now - this.config.analysis_window_hours * 60 * 60 * 1e3,
|
|
4318
|
+
now
|
|
4319
|
+
);
|
|
4320
|
+
return {
|
|
4321
|
+
total_events: this.events.length,
|
|
4322
|
+
total_alerts: this.alerts.length,
|
|
4323
|
+
baseline_override_rate: this.baselineWindow?.override_rate || 0,
|
|
4324
|
+
current_override_rate: currentWindow.override_rate
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
};
|
|
4328
|
+
|
|
2057
4329
|
// src/index.ts
|
|
2058
4330
|
var DEFAULT_RULES = [
|
|
2059
4331
|
// --- HTTP Proxy: Dangerous outbound calls ---
|
|
@@ -2545,16 +4817,30 @@ var index_default = PolicyEngine;
|
|
|
2545
4817
|
0 && (module.exports = {
|
|
2546
4818
|
AdaptiveThresholdManager,
|
|
2547
4819
|
AutoHardenEngine,
|
|
4820
|
+
ContextAccelerator,
|
|
4821
|
+
ContributionSettlementEngine,
|
|
2548
4822
|
CostGateEnhancedEngine,
|
|
4823
|
+
DEFAULT_CA_CONFIG,
|
|
4824
|
+
DEFAULT_GE_CONFIG,
|
|
4825
|
+
DEFAULT_HARD_RULES,
|
|
2549
4826
|
DEFAULT_RULES,
|
|
4827
|
+
DEFAULT_SCORING_WEIGHTS,
|
|
4828
|
+
DEFAULT_TSA_CONFIG,
|
|
2550
4829
|
EvolutionChannelEngine,
|
|
2551
4830
|
FeatureSwitchesManager,
|
|
4831
|
+
GovernanceEnhancer,
|
|
2552
4832
|
MultiLevelBudgetEngine,
|
|
4833
|
+
OverrideDriftAnalyzer,
|
|
2553
4834
|
PolicyEngine,
|
|
2554
4835
|
PricingRulesEngine,
|
|
2555
4836
|
RecalculationEngine,
|
|
2556
4837
|
SOVR_FEATURE_SWITCHES,
|
|
4838
|
+
SelfConstraintEngine,
|
|
2557
4839
|
SemanticDriftDetectorEngine,
|
|
4840
|
+
TimeSeriesAggregator,
|
|
4841
|
+
TrendAlertEngine,
|
|
4842
|
+
TwoPhaseRouter,
|
|
4843
|
+
ValuationModel,
|
|
2558
4844
|
compileFromJSON,
|
|
2559
4845
|
compileRuleSet,
|
|
2560
4846
|
createAutoHardenEngine,
|