1use crate::types::*;
103
104pub const TIER_BIT_A: u32 = 1 << 0; pub const TIER_BIT_B: u32 = 1 << 1; pub const TIER_BIT_C: u32 = 1 << 2; pub const TIER_BIT_D: u32 = 1 << 3; pub const TIER_BIT_E: u32 = 1 << 4; pub const TIER_BIT_F: u32 = 1 << 5; pub const TIER_BIT_EXTRA: u32 = 1 << 6; pub const TIER_BIT_G: u32 = 1 << 7; pub const TIER_BIT_H: u32 = 1 << 8; pub const TIER_BIT_I: u32 = 1 << 9; pub const TIER_BIT_J: u32 = 1 << 10; pub const TIER_BIT_K: u32 = 1 << 11; pub const TIER_BIT_L: u32 = 1 << 12; pub const TIER_BIT_M: u32 = 1 << 13; pub const TIER_BIT_N: u32 = 1 << 14; pub const TIER_BIT_O: u32 = 1 << 15; pub const TIER_BIT_P: u32 = 1 << 16; pub const TIER_BIT_Q: u32 = 1 << 17; pub const TIER_BIT_R: u32 = 1 << 18; pub const TIER_BIT_S: u32 = 1 << 19; pub const TIER_BIT_T: u32 = 1 << 20; pub const TIER_BIT_U: u32 = 1 << 21; pub const TIER_BIT_V: u32 = 1 << 22; pub const TIER_BIT_W: u32 = 1 << 23; pub const TIER_BIT_X: u32 = 1 << 24; pub const TIER_BIT_Y: u32 = 1 << 25; pub const TIER_BIT_Z: u32 = 1 << 26; pub const TIER_BIT_AA: u32 = 1 << 27; pub const TIER_BIT_BB: u32 = 1 << 28; pub const TIER_BIT_CC: u32 = 1 << 29; pub const TIER_BIT_DD: u32 = 1 << 30; pub const TIER_BIT_EE: u32 = 1 << 31; pub fn affinity_tiers_for(reason_code: ReasonCode, min_correlation_count: u16) -> u32 {
158 let base = match reason_code {
159 ReasonCode::SustainedOutwardDrift => {
161 TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_S | TIER_BIT_U | TIER_BIT_T
162 }
163 ReasonCode::AbruptSlewViolation => {
165 TIER_BIT_A | TIER_BIT_B | TIER_BIT_I | TIER_BIT_N | TIER_BIT_O | TIER_BIT_EXTRA
166 }
167 ReasonCode::RecurrentBoundaryGrazing => {
169 TIER_BIT_K | TIER_BIT_M | TIER_BIT_U
170 }
171 ReasonCode::DriftWithRecovery => {
173 TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_T
174 }
175 ReasonCode::BoundaryApproach => {
177 TIER_BIT_I | TIER_BIT_J | TIER_BIT_K | TIER_BIT_M
178 }
179 ReasonCode::EnvelopeViolation => {
181 TIER_BIT_A | TIER_BIT_B | TIER_BIT_E | TIER_BIT_R
182 }
183 ReasonCode::SingleCrossing => {
185 TIER_BIT_A | TIER_BIT_F
186 }
187 ReasonCode::Admissible => u32::MAX,
189 };
190 let multivariate = if min_correlation_count >= 3 {
192 TIER_BIT_C | TIER_BIT_L | TIER_BIT_EXTRA
193 } else { 0 };
194 base | multivariate
195}
196
197pub struct HeuristicsBank<const MAX: usize> {
200 entries: [Option<HeuristicEntry>; MAX],
201 count: usize,
202}
203
204impl<const MAX: usize> HeuristicsBank<MAX> {
205 pub fn with_canonical_motifs() -> Self {
208 let mut bank = Self {
209 entries: [None; MAX],
210 count: 0,
211 };
212
213 let canonical: &[HeuristicEntry] = &[
214 HeuristicEntry {
217 motif_class: MotifClass::MemoryLeakDrift,
218 reason_code: ReasonCode::SustainedOutwardDrift,
219 candidate_interpretation: "sustained monotonic memory-consumption drift; may correspond to object-retention bugs",
220 provenance: Provenance::FrameworkDesign,
221 recommended_action: PolicyState::Review,
222 drift_threshold: 0.6,
223 slew_threshold: 0.0,
224 boundary_density_threshold: 0.4,
225 min_correlation_count: 1,
226 max_correlation_count: u16::MAX,
227 min_duration_windows: 5,
228 max_duration_windows: u16::MAX,
229 weight_drift: 1.5,
230 weight_slew: 0.3,
231 weight_boundary: 1.0,
232 weight_correlation: 0.5,
233 weight_duration: 1.2,
234 evidence_dataset: "FrameworkDesign",
235 evidence_dataset_doi: "",
236 dashboard_hint: "Inspect process RSS / heap-used and gc.duration over the past hour",
237 taxonomy_ref: "IEEE 24765: 'memory leak'; A-L-R: latent fault → error",
238 affinity_tiers: TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_S | TIER_BIT_U | TIER_BIT_T | TIER_BIT_X | TIER_BIT_Y,
239 confuser_motif: Some(MotifClass::ConnectionPoolExhaustionDrift),
240 margin_vs_confuser_threshold: 0.10,
241 primary_witness_tiers: TIER_BIT_I | TIER_BIT_M | TIER_BIT_S,
242 primary_witness_detectors: &["mann_kendall", "monotone_leak", "theil_sen_residual"],
243 },
244 HeuristicEntry {
245 motif_class: MotifClass::CascadingTimeoutSlew,
246 reason_code: ReasonCode::AbruptSlewViolation,
247 candidate_interpretation: "step-change latency propagating across dependency chain; may correspond to upstream failure",
248 provenance: Provenance::DatasetObserved,
249 recommended_action: PolicyState::Escalate,
250 drift_threshold: 0.0,
251 slew_threshold: 0.5,
252 boundary_density_threshold: 0.0,
253 min_correlation_count: 3,
254 max_correlation_count: u16::MAX,
255 min_duration_windows: 2,
256 max_duration_windows: 30,
257 weight_drift: 0.2,
258 weight_slew: 1.5,
259 weight_boundary: 0.5,
260 weight_correlation: 1.5,
261 weight_duration: 0.8,
262 evidence_dataset: "tadbench_trainticket_F04",
263 evidence_dataset_doi: "10.5281/zenodo.6979726",
264 dashboard_hint: "Inspect ${ROOT_CAUSE_SERVICE} (signal ${ROOT_CAUSE_INDEX}); ${CONTRIBUTING_COUNT} services contribute over ${DURATION_WINDOWS} windows; peak slew ${PEAK_SLEW}",
265 taxonomy_ref: "IEEE 24765: 'fault propagation'; A-L-R: error → service-failure",
266 affinity_tiers: TIER_BIT_C | TIER_BIT_L | TIER_BIT_EXTRA | TIER_BIT_B | TIER_BIT_M | TIER_BIT_V | TIER_BIT_X,
267 confuser_motif: Some(MotifClass::DependencySlowdown),
268 margin_vs_confuser_threshold: 0.10,
269 primary_witness_tiers: TIER_BIT_C | TIER_BIT_L | TIER_BIT_EXTRA,
270 primary_witness_detectors: &["correlation_break", "lof", "causal_lag"],
271 },
272 HeuristicEntry {
273 motif_class: MotifClass::DeploymentRegressionSlew,
274 reason_code: ReasonCode::AbruptSlewViolation,
275 candidate_interpretation: "abrupt baseline shift coinciding with deployment; structural step function",
276 provenance: Provenance::DatasetObserved,
277 recommended_action: PolicyState::Escalate,
278 drift_threshold: 0.0,
279 slew_threshold: 0.8,
280 boundary_density_threshold: 0.0,
281 min_correlation_count: 1,
282 max_correlation_count: 2,
283 min_duration_windows: 1,
284 max_duration_windows: u16::MAX,
285 weight_drift: 0.0,
286 weight_slew: 2.0,
287 weight_boundary: 0.5,
288 weight_correlation: 0.3,
289 weight_duration: 0.2,
290 evidence_dataset: "tadbench_trainticket_F11",
291 evidence_dataset_doi: "10.5281/zenodo.6979726",
292 dashboard_hint: "Single-service step shift on ${ROOT_CAUSE_SERVICE} (signal ${ROOT_CAUSE_INDEX}); peak slew ${PEAK_SLEW}; correlate with deployment log near window ${DURATION_WINDOWS}; consider rollback",
293 taxonomy_ref: "IEEE 24765: 'regression'; A-L-R: design fault → error",
294 affinity_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_I | TIER_BIT_N | TIER_BIT_O | TIER_BIT_X | TIER_BIT_Y | TIER_BIT_V,
295 confuser_motif: Some(MotifClass::CircuitBreakerOpenShift),
296 margin_vs_confuser_threshold: 0.10,
297 primary_witness_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_N | TIER_BIT_X,
298 primary_witness_detectors: &["page_hinkley", "pelt", "pettitt_test"],
299 },
300 HeuristicEntry {
301 motif_class: MotifClass::CacheDegradationGrazing,
302 reason_code: ReasonCode::RecurrentBoundaryGrazing,
303 candidate_interpretation: "oscillatory approach to SLO boundary; may correspond to cache eviction patterns",
304 provenance: Provenance::FrameworkDesign,
305 recommended_action: PolicyState::Watch,
306 drift_threshold: 0.3,
307 slew_threshold: 0.0,
308 boundary_density_threshold: 0.5,
309 min_correlation_count: 1,
310 max_correlation_count: u16::MAX,
311 min_duration_windows: 4,
312 max_duration_windows: u16::MAX,
313 weight_drift: 0.6,
314 weight_slew: 0.4,
315 weight_boundary: 1.8,
316 weight_correlation: 0.4,
317 weight_duration: 1.0,
318 evidence_dataset: "FrameworkDesign",
319 evidence_dataset_doi: "",
320 dashboard_hint: "Inspect cache hit-rate and eviction rate of the affected service",
321 taxonomy_ref: "IEEE 24765: 'performance degradation'; A-L-R: marginal-state error",
322 affinity_tiers: TIER_BIT_K | TIER_BIT_M | TIER_BIT_U | TIER_BIT_F | TIER_BIT_Z | TIER_BIT_AA,
323 confuser_motif: Some(MotifClass::GcPressureOscillation),
324 margin_vs_confuser_threshold: 0.10,
325 primary_witness_tiers: TIER_BIT_K | TIER_BIT_M | TIER_BIT_U,
326 primary_witness_detectors: &["autocorrelation_peak", "limit_cycle", "flap"],
327 },
328 HeuristicEntry {
329 motif_class: MotifClass::ConnectionPoolExhaustionDrift,
330 reason_code: ReasonCode::SustainedOutwardDrift,
331 candidate_interpretation: "slow positive drift in queue depth + latency with increasing variance",
332 provenance: Provenance::DatasetObserved,
333 recommended_action: PolicyState::Review,
334 drift_threshold: 0.5,
335 slew_threshold: 0.0,
336 boundary_density_threshold: 0.4,
337 min_correlation_count: 1,
338 max_correlation_count: u16::MAX,
339 min_duration_windows: 5,
340 max_duration_windows: u16::MAX,
341 weight_drift: 1.4,
342 weight_slew: 0.2,
343 weight_boundary: 1.0,
344 weight_correlation: 0.6,
345 weight_duration: 1.1,
346 evidence_dataset: "tadbench_trainticket_F19",
347 evidence_dataset_doi: "10.5281/zenodo.6979726",
348 dashboard_hint: "Inspect connection pool waiting queue + active connections + idle timeout",
349 taxonomy_ref: "IEEE 24765: 'resource exhaustion'; A-L-R: error build-up",
350 affinity_tiers: TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_E | TIER_BIT_X | TIER_BIT_Y,
351 confuser_motif: Some(MotifClass::MemoryLeakDrift),
352 margin_vs_confuser_threshold: 0.10,
353 primary_witness_tiers: TIER_BIT_I | TIER_BIT_E | TIER_BIT_M,
354 primary_witness_detectors: &["monotone_leak", "saturation_chain", "theil_sen_residual"],
355 },
356 HeuristicEntry {
357 motif_class: MotifClass::GcPressureOscillation,
358 reason_code: ReasonCode::RecurrentBoundaryGrazing,
359 candidate_interpretation: "periodic slew events coinciding with GC pauses; bounded oscillation",
360 provenance: Provenance::FrameworkDesign,
361 recommended_action: PolicyState::Watch,
362 drift_threshold: 0.0,
363 slew_threshold: 0.2,
364 boundary_density_threshold: 0.4,
365 min_correlation_count: 1,
366 max_correlation_count: 3,
367 min_duration_windows: 3,
368 max_duration_windows: u16::MAX,
369 weight_drift: 0.2,
370 weight_slew: 1.4,
371 weight_boundary: 1.4,
372 weight_correlation: 0.4,
373 weight_duration: 0.6,
374 evidence_dataset: "FrameworkDesign",
375 evidence_dataset_doi: "",
376 dashboard_hint: "Inspect gc.collection.count + gc.duration histograms",
377 taxonomy_ref: "IEEE 24765: 'stop-the-world pause'; A-L-R: transient error",
378 affinity_tiers: TIER_BIT_K | TIER_BIT_F | TIER_BIT_M | TIER_BIT_S | TIER_BIT_Z | TIER_BIT_AA,
379 confuser_motif: Some(MotifClass::CacheDegradationGrazing),
380 margin_vs_confuser_threshold: 0.10,
381 primary_witness_tiers: TIER_BIT_K | TIER_BIT_F | TIER_BIT_Z,
382 primary_witness_detectors: &["autocorrelation_peak", "sawtooth_ramp", "welch_psd"],
383 },
384 HeuristicEntry {
385 motif_class: MotifClass::ErrorRateEscalation,
386 reason_code: ReasonCode::SustainedOutwardDrift,
387 candidate_interpretation: "sustained positive drift in error rate",
388 provenance: Provenance::FrameworkDesign,
389 recommended_action: PolicyState::Escalate,
390 drift_threshold: 0.7,
391 slew_threshold: 0.0,
392 boundary_density_threshold: 0.3,
393 min_correlation_count: 1,
394 max_correlation_count: u16::MAX,
395 min_duration_windows: 3,
396 max_duration_windows: u16::MAX,
397 weight_drift: 1.8,
398 weight_slew: 0.3,
399 weight_boundary: 0.8,
400 weight_correlation: 0.7,
401 weight_duration: 1.0,
402 evidence_dataset: "FrameworkDesign",
403 evidence_dataset_doi: "",
404 dashboard_hint: "Inspect HTTP 5xx rate by endpoint and recent deploys / config changes",
405 taxonomy_ref: "IEEE 24765: 'error escalation'; A-L-R: error → multi-failure regime",
406 affinity_tiers: TIER_BIT_A | TIER_BIT_G | TIER_BIT_Q | TIER_BIT_E | TIER_BIT_X | TIER_BIT_Y,
407 confuser_motif: Some(MotifClass::PacketLossErrorEscalation),
408 margin_vs_confuser_threshold: 0.10,
409 primary_witness_tiers: TIER_BIT_A | TIER_BIT_G | TIER_BIT_E,
410 primary_witness_detectors: &["chi_squared_proportion", "ddm", "ecdd"],
411 },
412 HeuristicEntry {
413 motif_class: MotifClass::DependencySlowdown,
414 reason_code: ReasonCode::SustainedOutwardDrift,
415 candidate_interpretation: "gradual latency increase in upstream dependency",
416 provenance: Provenance::FrameworkDesign,
417 recommended_action: PolicyState::Review,
418 drift_threshold: 0.4,
419 slew_threshold: 0.0,
420 boundary_density_threshold: 0.3,
421 min_correlation_count: 1,
422 max_correlation_count: u16::MAX,
423 min_duration_windows: 5,
424 max_duration_windows: u16::MAX,
425 weight_drift: 1.3,
426 weight_slew: 0.3,
427 weight_boundary: 0.7,
428 weight_correlation: 0.7,
429 weight_duration: 0.9,
430 evidence_dataset: "FrameworkDesign",
431 evidence_dataset_doi: "",
432 dashboard_hint: "Inspect the upstream service's latency distribution; check its dependencies",
433 taxonomy_ref: "IEEE 24765: 'performance degradation upstream'; A-L-R: external fault",
434 affinity_tiers: TIER_BIT_C | TIER_BIT_L | TIER_BIT_M | TIER_BIT_EXTRA | TIER_BIT_V | TIER_BIT_X,
435 confuser_motif: Some(MotifClass::CascadingTimeoutSlew),
436 margin_vs_confuser_threshold: 0.10,
437 primary_witness_tiers: TIER_BIT_C | TIER_BIT_L | TIER_BIT_M,
438 primary_witness_detectors: &["causal_lag", "correlation_break", "lof"],
439 },
440 HeuristicEntry {
441 motif_class: MotifClass::ResourceSaturation,
442 reason_code: ReasonCode::SustainedOutwardDrift,
443 candidate_interpretation: "CPU/memory/disk approaching ceiling; concave-up drift",
444 provenance: Provenance::FrameworkDesign,
445 recommended_action: PolicyState::Review,
446 drift_threshold: 0.5,
447 slew_threshold: 0.0,
448 boundary_density_threshold: 0.5,
449 min_correlation_count: 1,
450 max_correlation_count: u16::MAX,
451 min_duration_windows: 5,
452 max_duration_windows: u16::MAX,
453 weight_drift: 1.4,
454 weight_slew: 0.3,
455 weight_boundary: 1.2,
456 weight_correlation: 0.5,
457 weight_duration: 1.0,
458 evidence_dataset: "FrameworkDesign",
459 evidence_dataset_doi: "",
460 dashboard_hint: "Inspect system resource gauges (CPU%, memory%, disk%) over the past hour",
461 taxonomy_ref: "IEEE 24765: 'resource saturation'; A-L-R: latent → manifest fault",
462 affinity_tiers: TIER_BIT_A | TIER_BIT_E | TIER_BIT_M | TIER_BIT_R | TIER_BIT_X | TIER_BIT_Y,
463 confuser_motif: Some(MotifClass::ConnectionPoolExhaustionDrift),
464 margin_vs_confuser_threshold: 0.10,
465 primary_witness_tiers: TIER_BIT_A | TIER_BIT_E | TIER_BIT_R,
466 primary_witness_detectors: &["saturation_chain", "monotone_leak", "mann_kendall"],
467 },
468 HeuristicEntry {
469 motif_class: MotifClass::QueueBackpressure,
470 reason_code: ReasonCode::SustainedOutwardDrift,
471 candidate_interpretation: "message queue depth growing monotonically",
472 provenance: Provenance::FrameworkDesign,
473 recommended_action: PolicyState::Review,
474 drift_threshold: 0.6,
475 slew_threshold: 0.0,
476 boundary_density_threshold: 0.3,
477 min_correlation_count: 1,
478 max_correlation_count: u16::MAX,
479 min_duration_windows: 4,
480 max_duration_windows: u16::MAX,
481 weight_drift: 1.5,
482 weight_slew: 0.2,
483 weight_boundary: 0.9,
484 weight_correlation: 0.5,
485 weight_duration: 1.1,
486 evidence_dataset: "FrameworkDesign",
487 evidence_dataset_doi: "",
488 dashboard_hint: "Inspect message-queue depth and consumer lag metrics",
489 taxonomy_ref: "IEEE 24765: 'back-pressure accumulation'; A-L-R: error build-up",
490 affinity_tiers: TIER_BIT_M | TIER_BIT_L | TIER_BIT_J | TIER_BIT_S | TIER_BIT_V | TIER_BIT_X | TIER_BIT_AA,
491 confuser_motif: Some(MotifClass::ConnectionPoolExhaustionDrift),
492 margin_vs_confuser_threshold: 0.10,
493 primary_witness_tiers: TIER_BIT_M | TIER_BIT_L,
494 primary_witness_detectors: &["backpressure", "mann_kendall", "mahalanobis"],
495 },
496
497 HeuristicEntry {
500 motif_class: MotifClass::RetryStormCascade,
501 reason_code: ReasonCode::SustainedOutwardDrift,
502 candidate_interpretation: "client retries amplify upstream load; both error rate and request rate rise together",
503 provenance: Provenance::DatasetObserved,
504 recommended_action: PolicyState::Escalate,
505 drift_threshold: 0.6,
506 slew_threshold: 0.3,
507 boundary_density_threshold: 0.4,
508 min_correlation_count: 2,
509 max_correlation_count: u16::MAX,
510 min_duration_windows: 3,
511 max_duration_windows: 60,
512 weight_drift: 1.3,
513 weight_slew: 1.0,
514 weight_boundary: 0.8,
515 weight_correlation: 1.4,
516 weight_duration: 0.7,
517 evidence_dataset: "tadbench_retry_storm",
518 evidence_dataset_doi: "10.5281/zenodo.6979726",
519 dashboard_hint: "Inspect retry-policy parameters and client-side request rate vs upstream success rate",
520 taxonomy_ref: "IEEE 24765: 'retry-induced amplification'; A-L-R: cascading error",
521 affinity_tiers: TIER_BIT_F | TIER_BIT_M | TIER_BIT_EXTRA | TIER_BIT_S | TIER_BIT_AA | TIER_BIT_Z,
522 confuser_motif: Some(MotifClass::CascadingTimeoutSlew),
523 margin_vs_confuser_threshold: 0.10,
524 primary_witness_tiers: TIER_BIT_F | TIER_BIT_EXTRA | TIER_BIT_M,
525 primary_witness_detectors: &["retry_storm", "causal_lag", "poisson_burst"],
526 },
527 HeuristicEntry {
528 motif_class: MotifClass::CircuitBreakerOpenShift,
529 reason_code: ReasonCode::AbruptSlewViolation,
530 candidate_interpretation: "circuit breaker transitions from CLOSED to OPEN; downstream calls fail fast",
531 provenance: Provenance::DatasetObserved,
532 recommended_action: PolicyState::Escalate,
533 drift_threshold: 0.0,
534 slew_threshold: 0.6,
535 boundary_density_threshold: 0.0,
536 min_correlation_count: 1,
537 max_correlation_count: 4,
538 min_duration_windows: 2,
539 max_duration_windows: u16::MAX,
540 weight_drift: 0.2,
541 weight_slew: 1.6,
542 weight_boundary: 0.4,
543 weight_correlation: 0.8,
544 weight_duration: 0.6,
545 evidence_dataset: "tadbench_circuit_breaker",
546 evidence_dataset_doi: "10.5281/zenodo.6979726",
547 dashboard_hint: "Inspect circuit-breaker state metrics and the underlying service's health",
548 taxonomy_ref: "IEEE 24765: 'fault tolerance mechanism state change'",
549 affinity_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_N | TIER_BIT_O | TIER_BIT_X | TIER_BIT_Y,
550 confuser_motif: Some(MotifClass::DeploymentRegressionSlew),
551 margin_vs_confuser_threshold: 0.10,
552 primary_witness_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_N,
553 primary_witness_detectors: &["cusum", "pelt", "binary_segmentation"],
554 },
555 HeuristicEntry {
556 motif_class: MotifClass::DatabaseLockContention,
557 reason_code: ReasonCode::SustainedOutwardDrift,
558 candidate_interpretation: "database lock contention; rising query latency and queue depth",
559 provenance: Provenance::DatasetObserved,
560 recommended_action: PolicyState::Review,
561 drift_threshold: 0.5,
562 slew_threshold: 0.2,
563 boundary_density_threshold: 0.4,
564 min_correlation_count: 2,
565 max_correlation_count: u16::MAX,
566 min_duration_windows: 5,
567 max_duration_windows: u16::MAX,
568 weight_drift: 1.2,
569 weight_slew: 0.6,
570 weight_boundary: 1.0,
571 weight_correlation: 1.0,
572 weight_duration: 1.0,
573 evidence_dataset: "tadbench_db_lock",
574 evidence_dataset_doi: "10.5281/zenodo.6979726",
575 dashboard_hint: "Inspect database lock-wait stats and slow-query log for the active transactions",
576 taxonomy_ref: "IEEE 24765: 'concurrency fault'; A-L-R: synchronisation error",
577 affinity_tiers: TIER_BIT_L | TIER_BIT_M | TIER_BIT_C | TIER_BIT_EXTRA | TIER_BIT_V | TIER_BIT_AA,
578 confuser_motif: Some(MotifClass::DependencySlowdown),
579 margin_vs_confuser_threshold: 0.10,
580 primary_witness_tiers: TIER_BIT_L | TIER_BIT_M | TIER_BIT_EXTRA,
581 primary_witness_detectors: &["causal_lag", "correlation_break", "mahalanobis"],
582 },
583 HeuristicEntry {
584 motif_class: MotifClass::AuthenticationFailureSpike,
585 reason_code: ReasonCode::AbruptSlewViolation,
586 candidate_interpretation: "authentication subsystem partial outage; spike in 401/403 + downstream re-auth retries",
587 provenance: Provenance::DatasetObserved,
588 recommended_action: PolicyState::Escalate,
589 drift_threshold: 0.0,
590 slew_threshold: 0.5,
591 boundary_density_threshold: 0.2,
592 min_correlation_count: 1,
593 max_correlation_count: u16::MAX,
594 min_duration_windows: 1,
595 max_duration_windows: 30,
596 weight_drift: 0.3,
597 weight_slew: 1.5,
598 weight_boundary: 0.6,
599 weight_correlation: 0.9,
600 weight_duration: 0.5,
601 evidence_dataset: "tadbench_auth_fail",
602 evidence_dataset_doi: "10.5281/zenodo.6979726",
603 dashboard_hint: "Inspect auth-service health, token-issuance rate, and 401/403 distribution",
604 taxonomy_ref: "IEEE 24765: 'authentication subsystem failure'",
605 affinity_tiers: TIER_BIT_F | TIER_BIT_M | TIER_BIT_A | TIER_BIT_AA | TIER_BIT_Z | TIER_BIT_Y,
606 confuser_motif: Some(MotifClass::EpisodicTransientSpike),
607 margin_vs_confuser_threshold: 0.10,
608 primary_witness_tiers: TIER_BIT_F | TIER_BIT_M,
609 primary_witness_detectors: &["poisson_burst", "burst_after_silence", "flap"],
610 },
611 HeuristicEntry {
612 motif_class: MotifClass::ConfigDriftRegression,
613 reason_code: ReasonCode::AbruptSlewViolation,
614 candidate_interpretation: "step shift coinciding with version-config change",
615 provenance: Provenance::DatasetObserved,
616 recommended_action: PolicyState::Escalate,
617 drift_threshold: 0.0,
618 slew_threshold: 0.6,
619 boundary_density_threshold: 0.0,
620 min_correlation_count: 1,
621 max_correlation_count: 3,
622 min_duration_windows: 1,
623 max_duration_windows: u16::MAX,
624 weight_drift: 0.2,
625 weight_slew: 1.7,
626 weight_boundary: 0.5,
627 weight_correlation: 0.5,
628 weight_duration: 0.4,
629 evidence_dataset: "trainticket_anomaly_version_config",
630 evidence_dataset_doi: "10.5281/zenodo.6979726",
631 dashboard_hint: "Diff config artefacts between the last good window and the current; consider rollback",
632 taxonomy_ref: "IEEE 24765: 'configuration regression'; A-L-R: design-time fault",
633 affinity_tiers: TIER_BIT_H | TIER_BIT_G | TIER_BIT_Q | TIER_BIT_X | TIER_BIT_Y,
634 confuser_motif: Some(MotifClass::DeploymentRegressionSlew),
635 margin_vs_confuser_threshold: 0.10,
636 primary_witness_tiers: TIER_BIT_H | TIER_BIT_G | TIER_BIT_Q,
637 primary_witness_detectors: &["wasserstein_1d", "ddm", "kl_divergence"],
638 },
639
640 HeuristicEntry {
643 motif_class: MotifClass::PacketLossErrorEscalation,
644 reason_code: ReasonCode::SustainedOutwardDrift,
645 candidate_interpretation: "network-layer packet loss elevates error rate; sustained positive drift",
646 provenance: Provenance::DatasetObserved,
647 recommended_action: PolicyState::Escalate,
648 drift_threshold: 0.6,
649 slew_threshold: 0.0,
650 boundary_density_threshold: 0.3,
651 min_correlation_count: 2,
652 max_correlation_count: u16::MAX,
653 min_duration_windows: 3,
654 max_duration_windows: u16::MAX,
655 weight_drift: 1.6,
656 weight_slew: 0.4,
657 weight_boundary: 0.8,
658 weight_correlation: 1.2,
659 weight_duration: 0.8,
660 evidence_dataset: "aiops_challenge_packet_loss",
661 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
662 dashboard_hint: "Inspect TCP retransmits / packet-loss counters at the network layer",
663 taxonomy_ref: "IEEE 24765: 'communication failure (lower layer)'",
664 affinity_tiers: TIER_BIT_A | TIER_BIT_G | TIER_BIT_F | TIER_BIT_E | TIER_BIT_AA | TIER_BIT_Z,
665 confuser_motif: Some(MotifClass::ErrorRateEscalation),
666 margin_vs_confuser_threshold: 0.10,
667 primary_witness_tiers: TIER_BIT_A | TIER_BIT_G | TIER_BIT_F,
668 primary_witness_detectors: &["chi_squared_proportion", "poisson_burst", "ddm"],
669 },
670 HeuristicEntry {
671 motif_class: MotifClass::NetworkDelayDependencyInflation,
672 reason_code: ReasonCode::SustainedOutwardDrift,
673 candidate_interpretation: "injected network delay on upstream link; gradual latency-increase pattern across consumers",
674 provenance: Provenance::DatasetObserved,
675 recommended_action: PolicyState::Review,
676 drift_threshold: 0.4,
677 slew_threshold: 0.0,
678 boundary_density_threshold: 0.3,
679 min_correlation_count: 2,
680 max_correlation_count: u16::MAX,
681 min_duration_windows: 5,
682 max_duration_windows: u16::MAX,
683 weight_drift: 1.3,
684 weight_slew: 0.2,
685 weight_boundary: 0.7,
686 weight_correlation: 1.3,
687 weight_duration: 1.1,
688 evidence_dataset: "aiops_challenge_network_delay",
689 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
690 dashboard_hint: "Inspect inter-service RTT histograms and link-level latency gauges",
691 taxonomy_ref: "IEEE 24765: 'communication-path performance fault'",
692 affinity_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_M | TIER_BIT_V | TIER_BIT_X,
693 confuser_motif: Some(MotifClass::DependencySlowdown),
694 margin_vs_confuser_threshold: 0.10,
695 primary_witness_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_M,
696 primary_witness_detectors: &["causal_lag", "lof", "correlation_break"],
697 },
698 HeuristicEntry {
699 motif_class: MotifClass::DiskIoSaturation,
700 reason_code: ReasonCode::SustainedOutwardDrift,
701 candidate_interpretation: "disk I/O saturation; concave-up latency drift on storage-bound services",
702 provenance: Provenance::DatasetObserved,
703 recommended_action: PolicyState::Review,
704 drift_threshold: 0.5,
705 slew_threshold: 0.0,
706 boundary_density_threshold: 0.5,
707 min_correlation_count: 1,
708 max_correlation_count: u16::MAX,
709 min_duration_windows: 4,
710 max_duration_windows: u16::MAX,
711 weight_drift: 1.4,
712 weight_slew: 0.3,
713 weight_boundary: 1.2,
714 weight_correlation: 0.6,
715 weight_duration: 1.0,
716 evidence_dataset: "aiops_challenge_disk_exhaustion",
717 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
718 dashboard_hint: "Inspect disk IOPS / await / queue depth; check for runaway log writes",
719 taxonomy_ref: "IEEE 24765: 'storage subsystem saturation'",
720 affinity_tiers: TIER_BIT_E | TIER_BIT_A | TIER_BIT_I | TIER_BIT_R | TIER_BIT_X | TIER_BIT_Y,
721 confuser_motif: Some(MotifClass::CpuSaturation),
722 margin_vs_confuser_threshold: 0.10,
723 primary_witness_tiers: TIER_BIT_E | TIER_BIT_I | TIER_BIT_R,
724 primary_witness_detectors: &["saturation_chain", "monotone_leak", "theil_sen_residual"],
725 },
726 HeuristicEntry {
727 motif_class: MotifClass::CpuSaturation,
728 reason_code: ReasonCode::SustainedOutwardDrift,
729 candidate_interpretation: "CPU saturation; latency drift with rising envelope occupancy",
730 provenance: Provenance::DatasetObserved,
731 recommended_action: PolicyState::Review,
732 drift_threshold: 0.5,
733 slew_threshold: 0.0,
734 boundary_density_threshold: 0.5,
735 min_correlation_count: 1,
736 max_correlation_count: u16::MAX,
737 min_duration_windows: 4,
738 max_duration_windows: u16::MAX,
739 weight_drift: 1.4,
740 weight_slew: 0.3,
741 weight_boundary: 1.2,
742 weight_correlation: 0.6,
743 weight_duration: 1.0,
744 evidence_dataset: "aiops_challenge_cpu_exhaustion",
745 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
746 dashboard_hint: "Inspect CPU utilisation, run-queue length, and thread-level scheduling latency",
747 taxonomy_ref: "IEEE 24765: 'compute resource saturation'",
748 affinity_tiers: TIER_BIT_E | TIER_BIT_A | TIER_BIT_I | TIER_BIT_R | TIER_BIT_X | TIER_BIT_Y,
749 confuser_motif: Some(MotifClass::DiskIoSaturation),
750 margin_vs_confuser_threshold: 0.10,
751 primary_witness_tiers: TIER_BIT_E | TIER_BIT_I | TIER_BIT_R,
752 primary_witness_detectors: &["saturation_chain", "monotone_leak", "theil_sen_residual"],
753 },
754 HeuristicEntry {
755 motif_class: MotifClass::JvmHeapPressure,
756 reason_code: ReasonCode::SustainedOutwardDrift,
757 candidate_interpretation: "JVM heap pressure; sustained latency drift with rising variance and elevated GC frequency",
758 provenance: Provenance::DatasetObserved,
759 recommended_action: PolicyState::Review,
760 drift_threshold: 0.6,
761 slew_threshold: 0.0,
762 boundary_density_threshold: 0.4,
763 min_correlation_count: 1,
764 max_correlation_count: u16::MAX,
765 min_duration_windows: 5,
766 max_duration_windows: u16::MAX,
767 weight_drift: 1.6,
768 weight_slew: 0.4,
769 weight_boundary: 1.0,
770 weight_correlation: 0.5,
771 weight_duration: 1.2,
772 evidence_dataset: "aiops_challenge_memory_exhaustion",
773 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
774 dashboard_hint: "Inspect jvm.memory.heap.used + gc.collection.count + minor/major GC ratio",
775 taxonomy_ref: "IEEE 24765: 'memory leak (JVM-specific)'; refines MemoryLeakDrift",
776 affinity_tiers: TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_E | TIER_BIT_X | TIER_BIT_Y,
777 confuser_motif: Some(MotifClass::MemoryLeakDrift),
778 margin_vs_confuser_threshold: 0.10,
779 primary_witness_tiers: TIER_BIT_I | TIER_BIT_M | TIER_BIT_E,
780 primary_witness_detectors: &["monotone_leak", "saturation_chain", "mann_kendall"],
781 },
782 HeuristicEntry {
783 motif_class: MotifClass::JvmGcPause,
784 reason_code: ReasonCode::AbruptSlewViolation,
785 candidate_interpretation: "JVM stop-the-world GC pause: distinct latency spikes with regular cadence",
786 provenance: Provenance::DatasetObserved,
787 recommended_action: PolicyState::Watch,
788 drift_threshold: 0.0,
789 slew_threshold: 0.4,
790 boundary_density_threshold: 0.3,
791 min_correlation_count: 1,
792 max_correlation_count: 3,
793 min_duration_windows: 1,
794 max_duration_windows: 10,
795 weight_drift: 0.2,
796 weight_slew: 1.6,
797 weight_boundary: 1.0,
798 weight_correlation: 0.4,
799 weight_duration: 0.4,
800 evidence_dataset: "aiops_challenge_jvm_resource_exhaustion",
801 evidence_dataset_doi: "AIOps-Challenge-2020-2021",
802 dashboard_hint: "Inspect gc.duration percentiles + STW pause histograms; consider GC tuning",
803 taxonomy_ref: "IEEE 24765: 'stop-the-world pause (JVM-specific)'; refines GcPressureOscillation",
804 affinity_tiers: TIER_BIT_K | TIER_BIT_F | TIER_BIT_M | TIER_BIT_Z | TIER_BIT_AA,
805 confuser_motif: Some(MotifClass::AuthenticationFailureSpike),
806 margin_vs_confuser_threshold: 0.10,
807 primary_witness_tiers: TIER_BIT_K | TIER_BIT_F,
808 primary_witness_detectors: &["welch_psd", "autocorrelation_peak", "poisson_burst"],
809 },
810
811 HeuristicEntry {
814 motif_class: MotifClass::ServiceGraphDriftPropagation,
815 reason_code: ReasonCode::SustainedOutwardDrift,
816 candidate_interpretation: "drift propagating along the service-call graph (multi-hop); affects sequentially-related services",
817 provenance: Provenance::DatasetObserved,
818 recommended_action: PolicyState::Escalate,
819 drift_threshold: 0.5,
820 slew_threshold: 0.2,
821 boundary_density_threshold: 0.4,
822 min_correlation_count: 4,
823 max_correlation_count: u16::MAX,
824 min_duration_windows: 5,
825 max_duration_windows: u16::MAX,
826 weight_drift: 1.4,
827 weight_slew: 0.7,
828 weight_boundary: 0.9,
829 weight_correlation: 1.7,
830 weight_duration: 1.0,
831 evidence_dataset: "multidim_localization_graph_propagation",
832 evidence_dataset_doi: "MultiDimension-Localization-NetManAIOps",
833 dashboard_hint: "Multi-hop drift propagation across ${CONTRIBUTING_COUNT} services; originator: ${ROOT_CAUSE_SERVICE} (signal ${ROOT_CAUSE_INDEX}); peak slew ${PEAK_SLEW}",
834 taxonomy_ref: "IEEE 24765: 'graph-structured fault propagation'",
835 affinity_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_G | TIER_BIT_EXTRA | TIER_BIT_V | TIER_BIT_X,
836 confuser_motif: Some(MotifClass::DependencySlowdown),
837 margin_vs_confuser_threshold: 0.10,
838 primary_witness_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_EXTRA,
839 primary_witness_detectors: &["correlation_matrix_distance", "correlation_break", "causal_lag"],
840 },
841 HeuristicEntry {
842 motif_class: MotifClass::HighDimAnomalyCluster,
843 reason_code: ReasonCode::SustainedOutwardDrift,
844 candidate_interpretation: "multi-metric correlated anomaly without single dominant signal; compound fault",
845 provenance: Provenance::DatasetObserved,
846 recommended_action: PolicyState::Review,
847 drift_threshold: 0.4,
848 slew_threshold: 0.2,
849 boundary_density_threshold: 0.3,
850 min_correlation_count: 6,
851 max_correlation_count: u16::MAX,
852 min_duration_windows: 4,
853 max_duration_windows: u16::MAX,
854 weight_drift: 1.0,
855 weight_slew: 0.6,
856 weight_boundary: 0.7,
857 weight_correlation: 2.0,
858 weight_duration: 0.9,
859 evidence_dataset: "multidim_localization_cluster",
860 evidence_dataset_doi: "MultiDimension-Localization-NetManAIOps",
861 dashboard_hint: "Inspect the multi-metric anomaly cluster as a unit; no single metric is dominant",
862 taxonomy_ref: "IEEE 24765: 'compound fault signature'",
863 affinity_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_R | TIER_BIT_V | TIER_BIT_Y,
864 confuser_motif: Some(MotifClass::MetricCorrelationCollapse),
865 margin_vs_confuser_threshold: 0.10,
866 primary_witness_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_R,
867 primary_witness_detectors: &["mahalanobis", "pca_reconstruction", "lof"],
868 },
869 HeuristicEntry {
870 motif_class: MotifClass::MetricCorrelationCollapse,
871 reason_code: ReasonCode::AbruptSlewViolation,
872 candidate_interpretation: "historically-correlated metrics decorrelate; structural regime shift",
873 provenance: Provenance::DatasetObserved,
874 recommended_action: PolicyState::Review,
875 drift_threshold: 0.0,
876 slew_threshold: 0.4,
877 boundary_density_threshold: 0.0,
878 min_correlation_count: 2,
879 max_correlation_count: u16::MAX,
880 min_duration_windows: 3,
881 max_duration_windows: u16::MAX,
882 weight_drift: 0.4,
883 weight_slew: 1.3,
884 weight_boundary: 0.5,
885 weight_correlation: 1.5,
886 weight_duration: 0.8,
887 evidence_dataset: "multidim_localization_correlation_collapse",
888 evidence_dataset_doi: "MultiDimension-Localization-NetManAIOps",
889 dashboard_hint: "Compare current pairwise metric correlations against the baseline correlation matrix",
890 taxonomy_ref: "IEEE 24765: 'structural model invalidation'",
891 affinity_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_EXTRA | TIER_BIT_V | TIER_BIT_AA,
892 confuser_motif: Some(MotifClass::HighDimAnomalyCluster),
893 margin_vs_confuser_threshold: 0.10,
894 primary_witness_tiers: TIER_BIT_L | TIER_BIT_C | TIER_BIT_EXTRA,
895 primary_witness_detectors: &["correlation_break", "correlation_matrix_distance", "mahalanobis"],
896 },
897
898 HeuristicEntry {
901 motif_class: MotifClass::LogVolumeAnomaly,
902 reason_code: ReasonCode::SustainedOutwardDrift,
903 candidate_interpretation: "log-frequency outward drift on a service; structural log-rate anomaly",
904 provenance: Provenance::DatasetObserved,
905 recommended_action: PolicyState::Review,
906 drift_threshold: 0.5,
907 slew_threshold: 0.0,
908 boundary_density_threshold: 0.3,
909 min_correlation_count: 1,
910 max_correlation_count: u16::MAX,
911 min_duration_windows: 3,
912 max_duration_windows: u16::MAX,
913 weight_drift: 1.4,
914 weight_slew: 0.3,
915 weight_boundary: 0.8,
916 weight_correlation: 0.6,
917 weight_duration: 0.9,
918 evidence_dataset: "deeptralog_log_volume",
919 evidence_dataset_doi: "DeepTraLog-ICSE-2022",
920 dashboard_hint: "Inspect log-volume gauges per severity per service over the past hour",
921 taxonomy_ref: "IEEE 24765: 'diagnostic-output anomaly'",
922 affinity_tiers: TIER_BIT_A | TIER_BIT_S | TIER_BIT_I | TIER_BIT_X | TIER_BIT_Y,
923 confuser_motif: Some(MotifClass::LogSeverityEscalation),
924 margin_vs_confuser_threshold: 0.10,
925 primary_witness_tiers: TIER_BIT_A | TIER_BIT_S | TIER_BIT_I,
926 primary_witness_detectors: &["chi_squared_proportion", "poisson_burst", "mann_kendall"],
927 },
928 HeuristicEntry {
929 motif_class: MotifClass::LogTraceTemporalDecorrelation,
930 reason_code: ReasonCode::AbruptSlewViolation,
931 candidate_interpretation: "log timing departs from trace timing pattern; instrumentation/temporal divergence",
932 provenance: Provenance::DatasetObserved,
933 recommended_action: PolicyState::Review,
934 drift_threshold: 0.0,
935 slew_threshold: 0.3,
936 boundary_density_threshold: 0.2,
937 min_correlation_count: 1,
938 max_correlation_count: u16::MAX,
939 min_duration_windows: 3,
940 max_duration_windows: u16::MAX,
941 weight_drift: 0.4,
942 weight_slew: 1.3,
943 weight_boundary: 0.7,
944 weight_correlation: 0.7,
945 weight_duration: 0.7,
946 evidence_dataset: "deeptralog_temporal_mismatch",
947 evidence_dataset_doi: "DeepTraLog-ICSE-2022",
948 dashboard_hint: "Inspect log-event timestamps vs trace-span timestamps for the same request IDs",
949 taxonomy_ref: "IEEE 24765: 'instrumentation-temporal divergence'",
950 affinity_tiers: TIER_BIT_L | TIER_BIT_T | TIER_BIT_EXTRA | TIER_BIT_V | TIER_BIT_AA,
951 confuser_motif: Some(MotifClass::ServiceGraphDriftPropagation),
952 margin_vs_confuser_threshold: 0.10,
953 primary_witness_tiers: TIER_BIT_L | TIER_BIT_T | TIER_BIT_EXTRA,
954 primary_witness_detectors: &["correlation_break", "transfer_entropy", "correlation_matrix_distance"],
955 },
956 HeuristicEntry {
957 motif_class: MotifClass::LogSeverityEscalation,
958 reason_code: ReasonCode::SustainedOutwardDrift,
959 candidate_interpretation: "log severity distribution shift (more WARN/ERROR proportionally)",
960 provenance: Provenance::DatasetObserved,
961 recommended_action: PolicyState::Escalate,
962 drift_threshold: 0.6,
963 slew_threshold: 0.0,
964 boundary_density_threshold: 0.4,
965 min_correlation_count: 1,
966 max_correlation_count: u16::MAX,
967 min_duration_windows: 3,
968 max_duration_windows: u16::MAX,
969 weight_drift: 1.6,
970 weight_slew: 0.3,
971 weight_boundary: 1.0,
972 weight_correlation: 0.7,
973 weight_duration: 0.9,
974 evidence_dataset: "deeptralog_severity_shift",
975 evidence_dataset_doi: "DeepTraLog-ICSE-2022",
976 dashboard_hint: "Inspect log-severity distribution histograms; compare against the healthy-window baseline",
977 taxonomy_ref: "IEEE 24765: 'diagnostic severity escalation'",
978 affinity_tiers: TIER_BIT_H | TIER_BIT_G | TIER_BIT_A | TIER_BIT_X | TIER_BIT_Y,
979 confuser_motif: Some(MotifClass::LogVolumeAnomaly),
980 margin_vs_confuser_threshold: 0.10,
981 primary_witness_tiers: TIER_BIT_H | TIER_BIT_G | TIER_BIT_A,
982 primary_witness_detectors: &["wasserstein_1d", "chi_squared_proportion", "ddm"],
983 },
984
985 HeuristicEntry {
988 motif_class: MotifClass::SaturationTrending,
989 reason_code: ReasonCode::SustainedOutwardDrift,
990 candidate_interpretation: "concave-up approach to a ceiling; generalises ResourceSaturation",
991 provenance: Provenance::FrameworkDesign,
992 recommended_action: PolicyState::Watch,
993 drift_threshold: 0.5,
994 slew_threshold: 0.1,
995 boundary_density_threshold: 0.5,
996 min_correlation_count: 1,
997 max_correlation_count: u16::MAX,
998 min_duration_windows: 6,
999 max_duration_windows: u16::MAX,
1000 weight_drift: 1.3,
1001 weight_slew: 0.6,
1002 weight_boundary: 1.4,
1003 weight_correlation: 0.5,
1004 weight_duration: 1.1,
1005 evidence_dataset: "FrameworkDesign",
1006 evidence_dataset_doi: "",
1007 dashboard_hint: "Project the current drift forward to estimate time-to-ceiling; consider scaling",
1008 taxonomy_ref: "IEEE 24765: 'asymptotic resource saturation'",
1009 affinity_tiers: TIER_BIT_E | TIER_BIT_M | TIER_BIT_I | TIER_BIT_J | TIER_BIT_X | TIER_BIT_Y,
1010 confuser_motif: Some(MotifClass::ConnectionPoolExhaustionDrift),
1011 margin_vs_confuser_threshold: 0.10,
1012 primary_witness_tiers: TIER_BIT_E | TIER_BIT_M | TIER_BIT_I,
1013 primary_witness_detectors: &["saturation_chain", "monotone_leak", "mann_kendall"],
1014 },
1015 HeuristicEntry {
1016 motif_class: MotifClass::EpisodicTransientSpike,
1017 reason_code: ReasonCode::AbruptSlewViolation,
1018 candidate_interpretation: "short-duration high-slew event that self-resolves",
1019 provenance: Provenance::FrameworkDesign,
1020 recommended_action: PolicyState::Watch,
1021 drift_threshold: 0.0,
1022 slew_threshold: 0.5,
1023 boundary_density_threshold: 0.0,
1024 min_correlation_count: 1,
1025 max_correlation_count: u16::MAX,
1026 min_duration_windows: 1,
1027 max_duration_windows: 4,
1028 weight_drift: 0.2,
1029 weight_slew: 1.6,
1030 weight_boundary: 0.3,
1031 weight_correlation: 0.4,
1032 weight_duration: 0.3,
1033 evidence_dataset: "FrameworkDesign",
1034 evidence_dataset_doi: "",
1035 dashboard_hint: "Note the timestamp; correlate with cron / scheduled jobs / external triggers",
1036 taxonomy_ref: "IEEE 24765: 'transient-only error'; A-L-R: transient fault",
1037 affinity_tiers: TIER_BIT_A | TIER_BIT_F | TIER_BIT_M | TIER_BIT_AA | TIER_BIT_Z,
1038 confuser_motif: Some(MotifClass::AuthenticationFailureSpike),
1039 margin_vs_confuser_threshold: 0.10,
1040 primary_witness_tiers: TIER_BIT_A | TIER_BIT_F | TIER_BIT_M,
1041 primary_witness_detectors: &["poisson_burst", "burst_after_silence", "scalar_threshold_3sigma"],
1042 },
1043 HeuristicEntry {
1044 motif_class: MotifClass::RegressiveDriftWithRecovery,
1045 reason_code: ReasonCode::DriftWithRecovery,
1046 candidate_interpretation: "outward drift followed by return to baseline; self-healing structural transient",
1047 provenance: Provenance::FrameworkDesign,
1048 recommended_action: PolicyState::Watch,
1049 drift_threshold: 0.4,
1050 slew_threshold: 0.0,
1051 boundary_density_threshold: 0.2,
1052 min_correlation_count: 1,
1053 max_correlation_count: u16::MAX,
1054 min_duration_windows: 4,
1055 max_duration_windows: 30,
1056 weight_drift: 1.2,
1057 weight_slew: 0.3,
1058 weight_boundary: 0.6,
1059 weight_correlation: 0.5,
1060 weight_duration: 0.8,
1061 evidence_dataset: "FrameworkDesign",
1062 evidence_dataset_doi: "",
1063 dashboard_hint: "No action required if recovery confirmed; record as a near-miss for trend analysis",
1064 taxonomy_ref: "IEEE 24765: 'self-healing transient drift'",
1065 affinity_tiers: TIER_BIT_I | TIER_BIT_J | TIER_BIT_M | TIER_BIT_T | TIER_BIT_X | TIER_BIT_Y,
1066 confuser_motif: Some(MotifClass::MemoryLeakDrift),
1067 margin_vs_confuser_threshold: 0.10,
1068 primary_witness_tiers: TIER_BIT_I | TIER_BIT_J | TIER_BIT_M,
1069 primary_witness_detectors: &["theil_sen_residual", "monotone_leak", "mann_kendall"],
1070 },
1071 HeuristicEntry {
1072 motif_class: MotifClass::EnvelopeBoundaryApproach,
1073 reason_code: ReasonCode::BoundaryApproach,
1074 candidate_interpretation: "first-time approach to the SLO envelope without recurrence or persistent drift; marginal-state transient",
1075 provenance: Provenance::FrameworkDesign,
1076 recommended_action: PolicyState::Watch,
1077 drift_threshold: 0.0,
1078 slew_threshold: 0.0,
1079 boundary_density_threshold: 0.0,
1080 min_correlation_count: 1,
1081 max_correlation_count: u16::MAX,
1082 min_duration_windows: 1,
1083 max_duration_windows: u16::MAX,
1084 weight_drift: 0.5,
1085 weight_slew: 0.5,
1086 weight_boundary: 0.8,
1087 weight_correlation: 0.3,
1088 weight_duration: 0.4,
1089 evidence_dataset: "FrameworkDesign",
1090 evidence_dataset_doi: "",
1091 dashboard_hint: "Note the timestamp; if recurrence is observed in subsequent windows escalate to CacheDegradationGrazing",
1092 taxonomy_ref: "IEEE 24765: 'marginal-state transient'; A-L-R: dormant fault",
1093 affinity_tiers: TIER_BIT_A | TIER_BIT_J | TIER_BIT_I | TIER_BIT_X | TIER_BIT_Y,
1094 confuser_motif: Some(MotifClass::EnvelopeBreach),
1095 margin_vs_confuser_threshold: 0.10,
1096 primary_witness_tiers: TIER_BIT_A | TIER_BIT_I | TIER_BIT_J,
1097 primary_witness_detectors: &["scalar_threshold_3sigma", "mann_kendall", "theil_sen_residual"],
1098 },
1099 HeuristicEntry {
1100 motif_class: MotifClass::EnvelopeBreach,
1101 reason_code: ReasonCode::EnvelopeViolation,
1102 candidate_interpretation: "envelope breach without abrupt slew evidence; smooth threshold crossing",
1103 provenance: Provenance::FrameworkDesign,
1104 recommended_action: PolicyState::Escalate,
1105 drift_threshold: 0.0,
1106 slew_threshold: 0.0,
1107 boundary_density_threshold: 0.0,
1108 min_correlation_count: 1,
1109 max_correlation_count: u16::MAX,
1110 min_duration_windows: 1,
1111 max_duration_windows: u16::MAX,
1112 weight_drift: 0.7,
1113 weight_slew: 0.3,
1114 weight_boundary: 0.7,
1115 weight_correlation: 0.6,
1116 weight_duration: 0.6,
1117 evidence_dataset: "FrameworkDesign",
1118 evidence_dataset_doi: "",
1119 dashboard_hint: "Inspect SLO/SLA threshold + the affected service's value distribution; threshold may be too tight",
1120 taxonomy_ref: "IEEE 24765: 'threshold breach (smooth)'; A-L-R: error → manifest",
1121 affinity_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_R | TIER_BIT_E | TIER_BIT_X | TIER_BIT_Y,
1122 confuser_motif: Some(MotifClass::EnvelopeBoundaryApproach),
1123 margin_vs_confuser_threshold: 0.10,
1124 primary_witness_tiers: TIER_BIT_A | TIER_BIT_B | TIER_BIT_R,
1125 primary_witness_detectors: &["scalar_threshold_3sigma", "cusum", "page_hinkley"],
1126 },
1127 ];
1128
1129 let mut i = 0;
1130 while i < canonical.len() && i < MAX {
1131 bank.entries[i] = Some(canonical[i]);
1132 bank.count += 1;
1133 i += 1;
1134 }
1135 bank
1136 }
1137
1138 pub fn lookup(
1144 &self,
1145 reason_code: ReasonCode,
1146 drift_persistence: f64,
1147 slew_magnitude: f64,
1148 ) -> SemanticDisposition {
1149 let mut best_match: Option<MotifClass> = None;
1150 let mut best_score: f64 = 0.0;
1151 let mut best_provenance_rank: u8 = 0;
1152 let mut best_index: usize = usize::MAX;
1153
1154 let mut i = 0;
1155 while i < self.count {
1156 if let Some(entry) = &self.entries[i] {
1157 if entry.reason_code == reason_code {
1158 let mut score: f64 = 1.0; if drift_persistence >= entry.drift_threshold {
1160 score += drift_persistence;
1161 }
1162 if slew_magnitude >= entry.slew_threshold {
1163 score += slew_magnitude;
1164 }
1165 let prov_rank = provenance_rank(entry.provenance);
1166 let take = score > best_score
1167 || (score == best_score && prov_rank > best_provenance_rank)
1168 || (score == best_score && prov_rank == best_provenance_rank && i < best_index);
1169 if take {
1170 best_score = score;
1171 best_match = Some(entry.motif_class);
1172 best_provenance_rank = prov_rank;
1173 best_index = i;
1174 }
1175 }
1176 }
1177 i += 1;
1178 }
1179
1180 match best_match {
1181 Some(motif) => SemanticDisposition::Named(motif),
1182 None => SemanticDisposition::Unknown, }
1184 }
1185
1186 pub fn match_episode(
1201 &self,
1202 episode: &DebugEpisode,
1203 avg_drift_persistence: f64,
1204 avg_boundary_density: f64,
1205 ) -> SemanticDisposition {
1206 let mut best_match: Option<MotifClass> = None;
1207 let mut best_score: f64 = 0.0;
1208 let mut best_provenance_rank: u8 = 0;
1209 let mut best_index: usize = usize::MAX;
1210
1211 let correlation_count = episode.contributing_signal_count;
1212 let duration_windows: u16 = if episode.end_window >= episode.start_window {
1213 let d = episode.end_window - episode.start_window + 1;
1214 if d > u16::MAX as u64 { u16::MAX } else { d as u16 }
1215 } else {
1216 0
1217 };
1218 let peak_slew = episode.structural_signature.peak_slew_magnitude;
1219 let slew_mag = if peak_slew >= 0.0 { peak_slew } else { -peak_slew };
1220
1221 let mut i = 0;
1222 while i < self.count {
1223 if let Some(entry) = &self.entries[i] {
1224 if entry.reason_code != episode.primary_reason_code {
1225 i += 1;
1226 continue;
1227 }
1228 if correlation_count < entry.min_correlation_count
1229 || correlation_count > entry.max_correlation_count
1230 {
1231 i += 1;
1232 continue;
1233 }
1234 if duration_windows < entry.min_duration_windows
1235 || duration_windows > entry.max_duration_windows
1236 {
1237 i += 1;
1238 continue;
1239 }
1240
1241 let mut score: f64 = 1.0; if avg_drift_persistence >= entry.drift_threshold {
1244 score += entry.weight_drift * avg_drift_persistence;
1245 }
1246 if slew_mag >= entry.slew_threshold {
1247 score += entry.weight_slew * slew_mag;
1248 }
1249 if avg_boundary_density >= entry.boundary_density_threshold {
1250 score += entry.weight_boundary * avg_boundary_density;
1251 }
1252 score += entry.weight_correlation * (correlation_count as f64) * 0.1;
1255 score += entry.weight_duration * (duration_windows as f64) * 0.05;
1258
1259 let prov_rank = provenance_rank(entry.provenance);
1260 let take = score > best_score
1261 || (score == best_score && prov_rank > best_provenance_rank)
1262 || (score == best_score && prov_rank == best_provenance_rank && i < best_index);
1263 if take {
1264 best_score = score;
1265 best_match = Some(entry.motif_class);
1266 best_provenance_rank = prov_rank;
1267 best_index = i;
1268 }
1269 }
1270 i += 1;
1271 }
1272
1273 match best_match {
1274 Some(motif) => SemanticDisposition::Named(motif),
1275 None => SemanticDisposition::Unknown,
1276 }
1277 }
1278
1279 pub fn match_episode_with_confidence(
1286 &self,
1287 episode: &DebugEpisode,
1288 avg_drift_persistence: f64,
1289 avg_boundary_density: f64,
1290 ) -> MatchConfidence {
1291 let mut best_match: Option<MotifClass> = None;
1292 let mut best_score: f64 = 0.0;
1293 let mut best_provenance_rank: u8 = 0;
1294 let mut best_index: usize = usize::MAX;
1295 let mut runner_up_match: Option<MotifClass> = None;
1296 let mut runner_up_score: f64 = 0.0;
1297
1298 let correlation_count = episode.contributing_signal_count;
1299 let duration_windows: u16 = if episode.end_window >= episode.start_window {
1300 let d = episode.end_window - episode.start_window + 1;
1301 if d > u16::MAX as u64 { u16::MAX } else { d as u16 }
1302 } else {
1303 0
1304 };
1305 let peak_slew = episode.structural_signature.peak_slew_magnitude;
1306 let slew_mag = if peak_slew >= 0.0 { peak_slew } else { -peak_slew };
1307
1308 let mut i = 0;
1309 while i < self.count {
1310 if let Some(entry) = &self.entries[i] {
1311 if entry.reason_code != episode.primary_reason_code {
1312 i += 1;
1313 continue;
1314 }
1315 if correlation_count < entry.min_correlation_count
1316 || correlation_count > entry.max_correlation_count
1317 {
1318 i += 1;
1319 continue;
1320 }
1321 if duration_windows < entry.min_duration_windows
1322 || duration_windows > entry.max_duration_windows
1323 {
1324 i += 1;
1325 continue;
1326 }
1327
1328 let mut score: f64 = 1.0;
1329 if avg_drift_persistence >= entry.drift_threshold {
1330 score += entry.weight_drift * avg_drift_persistence;
1331 }
1332 if slew_mag >= entry.slew_threshold {
1333 score += entry.weight_slew * slew_mag;
1334 }
1335 if avg_boundary_density >= entry.boundary_density_threshold {
1336 score += entry.weight_boundary * avg_boundary_density;
1337 }
1338 score += entry.weight_correlation * (correlation_count as f64) * 0.1;
1339 score += entry.weight_duration * (duration_windows as f64) * 0.05;
1340
1341 let prov_rank = provenance_rank(entry.provenance);
1342 let take_top = score > best_score
1343 || (score == best_score && prov_rank > best_provenance_rank)
1344 || (score == best_score && prov_rank == best_provenance_rank && i < best_index);
1345 if take_top {
1346 if let Some(prev_top) = best_match {
1348 if score > runner_up_score
1349 || (score == runner_up_score && best_score > runner_up_score)
1350 {
1351 runner_up_match = Some(prev_top);
1352 runner_up_score = best_score;
1353 }
1354 }
1355 best_score = score;
1356 best_match = Some(entry.motif_class);
1357 best_provenance_rank = prov_rank;
1358 best_index = i;
1359 } else if score > runner_up_score {
1360 runner_up_score = score;
1361 runner_up_match = Some(entry.motif_class);
1362 }
1363 }
1364 i += 1;
1365 }
1366
1367 let disposition = match best_match {
1368 Some(m) => SemanticDisposition::Named(m),
1369 None => SemanticDisposition::Unknown,
1370 };
1371 let margin = if best_score > 0.0 {
1372 ((best_score - runner_up_score) / best_score).clamp(0.0, 1.0)
1373 } else {
1374 0.0
1375 };
1376 MatchConfidence {
1377 disposition,
1378 top_score: best_score,
1379 runner_up_score,
1380 runner_up_motif: runner_up_match,
1381 margin,
1382 tier_consensus_factor: 0.0,
1383 confuser_motif: None,
1384 confuser_score: 0.0,
1385 margin_vs_confuser: 0.0,
1386 }
1387 }
1388
1389 pub fn match_episode_with_consensus(
1408 &self,
1409 episode: &DebugEpisode,
1410 avg_drift_persistence: f64,
1411 avg_boundary_density: f64,
1412 episode_max_consensus: u8,
1413 max_detectors: u8,
1414 ) -> MatchConfidence {
1415 let consensus_factor = if max_detectors > 0 {
1416 episode_max_consensus as f64 / max_detectors as f64
1417 } else { 0.0 };
1418
1419 let mut best_match: Option<MotifClass> = None;
1420 let mut best_score: f64 = 0.0;
1421 let mut best_provenance_rank: u8 = 0;
1422 let mut best_index: usize = usize::MAX;
1423 let mut runner_up_match: Option<MotifClass> = None;
1424 let mut runner_up_score: f64 = 0.0;
1425
1426 let correlation_count = episode.contributing_signal_count;
1427 let duration_windows: u16 = if episode.end_window >= episode.start_window {
1428 let d = episode.end_window - episode.start_window + 1;
1429 if d > u16::MAX as u64 { u16::MAX } else { d as u16 }
1430 } else { 0 };
1431 let peak_slew = episode.structural_signature.peak_slew_magnitude;
1432 let slew_mag = if peak_slew >= 0.0 { peak_slew } else { -peak_slew };
1433
1434 let mut i = 0;
1435 while i < self.count {
1436 if let Some(entry) = &self.entries[i] {
1437 if entry.reason_code != episode.primary_reason_code {
1438 i += 1;
1439 continue;
1440 }
1441 if correlation_count < entry.min_correlation_count
1442 || correlation_count > entry.max_correlation_count
1443 {
1444 i += 1;
1445 continue;
1446 }
1447 if duration_windows < entry.min_duration_windows
1448 || duration_windows > entry.max_duration_windows
1449 {
1450 i += 1;
1451 continue;
1452 }
1453
1454 let mut score: f64 = 1.0;
1455 if avg_drift_persistence >= entry.drift_threshold {
1456 score += entry.weight_drift * avg_drift_persistence;
1457 }
1458 if slew_mag >= entry.slew_threshold {
1459 score += entry.weight_slew * slew_mag;
1460 }
1461 if avg_boundary_density >= entry.boundary_density_threshold {
1462 score += entry.weight_boundary * avg_boundary_density;
1463 }
1464 score += entry.weight_correlation * (correlation_count as f64) * 0.1;
1465 score += entry.weight_duration * (duration_windows as f64) * 0.05;
1466 score += consensus_factor;
1471
1472 let prov_rank = provenance_rank(entry.provenance);
1473 let take_top = score > best_score
1474 || (score == best_score && prov_rank > best_provenance_rank)
1475 || (score == best_score && prov_rank == best_provenance_rank && i < best_index);
1476 if take_top {
1477 if let Some(prev_top) = best_match {
1478 if score > runner_up_score
1479 || (score == runner_up_score && best_score > runner_up_score)
1480 {
1481 runner_up_match = Some(prev_top);
1482 runner_up_score = best_score;
1483 }
1484 }
1485 best_score = score;
1486 best_match = Some(entry.motif_class);
1487 best_provenance_rank = prov_rank;
1488 best_index = i;
1489 } else if score > runner_up_score {
1490 runner_up_score = score;
1491 runner_up_match = Some(entry.motif_class);
1492 }
1493 }
1494 i += 1;
1495 }
1496
1497 let disposition = match best_match {
1498 Some(m) => SemanticDisposition::Named(m),
1499 None => SemanticDisposition::Unknown,
1500 };
1501 let margin = if best_score > 0.0 {
1502 ((best_score - runner_up_score) / best_score).clamp(0.0, 1.0)
1503 } else { 0.0 };
1504 MatchConfidence {
1505 disposition,
1506 top_score: best_score,
1507 runner_up_score,
1508 runner_up_motif: runner_up_match,
1509 margin,
1510 tier_consensus_factor: 0.0,
1511 confuser_motif: None,
1512 confuser_score: 0.0,
1513 margin_vs_confuser: 0.0,
1514 }
1515 }
1516
1517 pub fn recommended_action(&self, motif: MotifClass) -> PolicyState {
1519 let mut i = 0;
1520 while i < self.count {
1521 if let Some(entry) = &self.entries[i] {
1522 if entry.motif_class == motif {
1523 return entry.recommended_action;
1524 }
1525 }
1526 i += 1;
1527 }
1528 PolicyState::Watch }
1530
1531 pub fn count(&self) -> usize {
1533 self.count
1534 }
1535
1536 pub fn entries_iter(&self) -> impl Iterator<Item = &HeuristicEntry> {
1546 self.entries[..self.count].iter().filter_map(|e| e.as_ref())
1547 }
1548
1549 pub fn entry_for(&self, motif: MotifClass) -> Option<&HeuristicEntry> {
1552 let mut i = 0;
1553 while i < self.count {
1554 if let Some(entry) = &self.entries[i] {
1555 if entry.motif_class == motif {
1556 return Some(entry);
1557 }
1558 }
1559 i += 1;
1560 }
1561 None
1562 }
1563
1564 pub fn effective_min_consensus(&self, entry: &HeuristicEntry, global_min: u8) -> u8 {
1579 let mut t = global_min as i16;
1580 match entry.provenance {
1581 Provenance::FieldValidated => { t -= 1; }
1582 Provenance::DatasetObserved => { }
1583 Provenance::FrameworkDesign => { t += 1; }
1584 }
1585 if entry.min_correlation_count >= 3 { t += 1; }
1586 if t < 1 { t = 1; }
1587 if t > 255 { t = 255; }
1588 t as u8
1589 }
1590
1591 pub fn effective_min_consensus_for_motif(
1595 &self, motif: MotifClass, global_min: u8,
1596 ) -> u8 {
1597 match self.entry_for(motif) {
1598 Some(entry) => self.effective_min_consensus(entry, global_min),
1599 None => global_min,
1600 }
1601 }
1602
1603 pub fn match_episode_with_tier_affinity(
1629 &self,
1630 episode: &DebugEpisode,
1631 avg_drift_persistence: f64,
1632 avg_boundary_density: f64,
1633 cell_tier_mask: &[u32],
1634 window_tier_mask: &[u32],
1635 num_signals: usize,
1636 max_active_tiers: u8,
1637 episode_max_consensus: u8,
1638 ) -> MatchConfidence {
1639 self.match_episode_with_tier_affinity_axes(
1641 episode, avg_drift_persistence, avg_boundary_density,
1642 cell_tier_mask, window_tier_mask, num_signals,
1643 max_active_tiers, episode_max_consensus,
1644 true, true, true,
1645 )
1646 }
1647
1648 pub fn match_episode_with_tier_affinity_axes(
1664 &self,
1665 episode: &DebugEpisode,
1666 avg_drift_persistence: f64,
1667 avg_boundary_density: f64,
1668 cell_tier_mask: &[u32],
1669 window_tier_mask: &[u32],
1670 num_signals: usize,
1671 max_active_tiers: u8,
1672 episode_max_consensus: u8,
1673 use_zero_tier_filter: bool,
1674 use_disambiguator_boost: bool,
1675 use_primary_witness_tier_gate: bool,
1676 ) -> MatchConfidence {
1677 let mut best_match: Option<MotifClass> = None;
1678 let mut best_score: f64 = 0.0;
1679 let mut best_provenance_rank: u8 = 0;
1680 let mut best_index: usize = usize::MAX;
1681 let mut best_consensus_factor: f64 = 0.0;
1682 let mut runner_up_match: Option<MotifClass> = None;
1683 let mut runner_up_score: f64 = 0.0;
1684 let mut entry_scores: [f64; MAX] = [0.0; MAX];
1687
1688 let mut entry_affinity: [u32; MAX] = [0; MAX];
1695 let mut entry_confuser_idx: [usize; MAX] = [usize::MAX; MAX];
1696 let mut p = 0;
1697 while p < self.count {
1698 if let Some(e) = &self.entries[p] {
1699 let aff = if e.affinity_tiers != 0 { e.affinity_tiers }
1700 else { affinity_tiers_for(e.reason_code, e.min_correlation_count) };
1701 if p < MAX { entry_affinity[p] = aff; }
1702 if let Some(cm) = e.confuser_motif {
1704 let mut q = 0;
1705 while q < self.count {
1706 if let Some(ce) = &self.entries[q] {
1707 if ce.motif_class == cm && q < MAX {
1708 entry_confuser_idx[p] = q;
1709 break;
1710 }
1711 }
1712 q += 1;
1713 }
1714 }
1715 }
1716 p += 1;
1717 }
1718
1719 let correlation_count = episode.contributing_signal_count;
1720 let duration_windows: u16 = if episode.end_window >= episode.start_window {
1721 let d = episode.end_window - episode.start_window + 1;
1722 if d > u16::MAX as u64 { u16::MAX } else { d as u16 }
1723 } else { 0 };
1724 let peak_slew = episode.structural_signature.peak_slew_magnitude;
1725 let slew_mag = if peak_slew >= 0.0 { peak_slew } else { -peak_slew };
1726
1727 let start_w = episode.start_window as usize;
1728 let end_w = episode.end_window as usize;
1729 let num_windows = window_tier_mask.len();
1730
1731 let mut i = 0;
1732 while i < self.count {
1733 if let Some(entry) = &self.entries[i] {
1734 if entry.reason_code != episode.primary_reason_code { i += 1; continue; }
1735 if correlation_count < entry.min_correlation_count
1736 || correlation_count > entry.max_correlation_count
1737 { i += 1; continue; }
1738 if duration_windows < entry.min_duration_windows
1739 || duration_windows > entry.max_duration_windows
1740 { i += 1; continue; }
1741
1742 let affinity = if entry.affinity_tiers != 0 {
1748 entry.affinity_tiers
1749 } else {
1750 affinity_tiers_for(entry.reason_code, entry.min_correlation_count)
1751 };
1752 let mut motif_consensus: u8 = 0;
1753 let mut w = start_w;
1754 while w <= end_w && w < num_windows {
1755 let win_bits = window_tier_mask[w] & affinity;
1756 let win_pop = win_bits.count_ones() as u8;
1757 let mut s = 0;
1758 while s < num_signals {
1759 let idx = w * num_signals + s;
1760 if idx < cell_tier_mask.len() {
1761 let cell_bits = cell_tier_mask[idx] & affinity;
1762 let total = cell_bits.count_ones() as u8 + win_pop;
1763 if total > motif_consensus { motif_consensus = total; }
1764 }
1765 s += 1;
1766 }
1767 w += 1;
1768 }
1769 let max_motif_tiers = (affinity.count_ones() as u8).max(1);
1770 let motif_consensus_capped = motif_consensus.min(max_motif_tiers);
1771 let consensus_factor = motif_consensus_capped as f64
1772 / max_motif_tiers as f64;
1773
1774 if use_zero_tier_filter
1785 && entry.affinity_tiers != 0
1786 && affinity != u32::MAX
1787 && motif_consensus == 0
1788 {
1789 i += 1;
1790 continue;
1791 }
1792
1793 if use_primary_witness_tier_gate && entry.primary_witness_tiers != 0 {
1802 let mut witness_fired = false;
1803 let mut wp = start_w;
1804 while wp <= end_w && wp < num_windows && !witness_fired {
1805 if window_tier_mask[wp] & entry.primary_witness_tiers != 0 {
1806 witness_fired = true;
1807 break;
1808 }
1809 let mut sp = 0;
1810 while sp < num_signals {
1811 let idxp = wp * num_signals + sp;
1812 if idxp < cell_tier_mask.len()
1813 && cell_tier_mask[idxp] & entry.primary_witness_tiers != 0
1814 {
1815 witness_fired = true;
1816 break;
1817 }
1818 sp += 1;
1819 }
1820 wp += 1;
1821 }
1822 if !witness_fired {
1823 i += 1;
1824 continue;
1825 }
1826 }
1827
1828 let mut score: f64 = 1.0;
1829 if avg_drift_persistence >= entry.drift_threshold {
1830 score += entry.weight_drift * avg_drift_persistence;
1831 }
1832 if slew_mag >= entry.slew_threshold {
1833 score += entry.weight_slew * slew_mag;
1834 }
1835 if avg_boundary_density >= entry.boundary_density_threshold {
1836 score += entry.weight_boundary * avg_boundary_density;
1837 }
1838 score += entry.weight_correlation * (correlation_count as f64) * 0.1;
1839 score += entry.weight_duration * (duration_windows as f64) * 0.05;
1840 score *= 1.0 + 0.5 * consensus_factor;
1851
1852 if use_disambiguator_boost && i < MAX && entry_confuser_idx[i] != usize::MAX {
1862 let confuser_aff = entry_affinity[entry_confuser_idx[i]];
1863 let disambig_mask = affinity & !confuser_aff;
1864 let disambig_max = disambig_mask.count_ones() as u8;
1865 if disambig_max > 0 {
1866 let mut disambig_fired: u8 = 0;
1868 let mut w2 = start_w;
1869 while w2 <= end_w && w2 < num_windows {
1870 let win_d = (window_tier_mask[w2] & disambig_mask).count_ones() as u8;
1871 let mut s2 = 0;
1872 while s2 < num_signals {
1873 let idx2 = w2 * num_signals + s2;
1874 if idx2 < cell_tier_mask.len() {
1875 let cell_d = (cell_tier_mask[idx2] & disambig_mask).count_ones() as u8;
1876 let total_d = (cell_d + win_d).min(disambig_max);
1877 if total_d > disambig_fired { disambig_fired = total_d; }
1878 }
1879 s2 += 1;
1880 }
1881 w2 += 1;
1882 }
1883 let disambig_factor = disambig_fired as f64 / disambig_max as f64;
1884 score *= 1.0 + 0.3 * disambig_factor;
1885 }
1886 }
1887
1888 if i < MAX { entry_scores[i] = score; }
1890
1891 let prov_rank = provenance_rank(entry.provenance);
1892 let take_top = score > best_score
1893 || (score == best_score && prov_rank > best_provenance_rank)
1894 || (score == best_score && prov_rank == best_provenance_rank && i < best_index);
1895 if take_top {
1896 if let Some(prev_top) = best_match {
1897 if score > runner_up_score
1898 || (score == runner_up_score && best_score > runner_up_score)
1899 {
1900 runner_up_match = Some(prev_top);
1901 runner_up_score = best_score;
1902 }
1903 }
1904 best_score = score;
1905 best_match = Some(entry.motif_class);
1906 best_provenance_rank = prov_rank;
1907 best_index = i;
1908 best_consensus_factor = consensus_factor;
1909 } else if score > runner_up_score {
1910 runner_up_score = score;
1911 runner_up_match = Some(entry.motif_class);
1912 }
1913 }
1914 i += 1;
1915 }
1916
1917 let _ = (max_active_tiers, episode_max_consensus); let disposition = match best_match {
1919 Some(m) => SemanticDisposition::Named(m),
1920 None => SemanticDisposition::Unknown,
1921 };
1922 let margin = if best_score > 0.0 {
1923 ((best_score - runner_up_score) / best_score).clamp(0.0, 1.0)
1924 } else { 0.0 };
1925
1926 let mut confuser_motif: Option<MotifClass> = None;
1930 let mut confuser_score = 0.0_f64;
1931 let mut margin_vs_confuser = 0.0_f64;
1932 if best_index < MAX {
1933 if let Some(top_entry) = &self.entries[best_index] {
1934 if let Some(c) = top_entry.confuser_motif {
1935 let mut k = 0;
1937 while k < self.count {
1938 if let Some(e) = &self.entries[k] {
1939 if e.motif_class == c && k < MAX {
1940 confuser_motif = Some(c);
1941 confuser_score = entry_scores[k];
1942 if best_score > 0.0 {
1943 margin_vs_confuser =
1944 ((best_score - confuser_score) / best_score).clamp(0.0, 1.0);
1945 }
1946 break;
1947 }
1948 }
1949 k += 1;
1950 }
1951 }
1952 }
1953 }
1954
1955 MatchConfidence {
1956 disposition,
1957 top_score: best_score,
1958 runner_up_score,
1959 runner_up_motif: runner_up_match,
1960 margin,
1961 tier_consensus_factor: best_consensus_factor,
1962 confuser_motif,
1963 confuser_score,
1964 margin_vs_confuser,
1965 }
1966 }
1967}
1968
1969impl<const MAX: usize> Default for HeuristicsBank<MAX> {
1973 fn default() -> Self {
1974 Self::with_canonical_motifs()
1975 }
1976}
1977
1978#[inline]
1980const fn provenance_rank(p: Provenance) -> u8 {
1981 match p {
1982 Provenance::FieldValidated => 3,
1983 Provenance::DatasetObserved => 2,
1984 Provenance::FrameworkDesign => 1,
1985 }
1986}
1987
1988#[cfg(test)]
1989mod tests {
1990 use super::*;
1991
1992 fn blank_episode_with(
1993 primary_reason: ReasonCode,
1994 peak_slew: f64,
1995 contributing: u16,
1996 start: u64,
1997 end: u64,
1998 drift_dir: DriftDirection,
1999 ) -> DebugEpisode {
2000 DebugEpisode {
2001 episode_id: 0,
2002 start_window: start,
2003 end_window: end,
2004 peak_grammar_state: GrammarState::Boundary,
2005 primary_reason_code: primary_reason,
2006 matched_motif: SemanticDisposition::Unknown,
2007 policy_state: PolicyState::Review,
2008 contributing_signal_count: contributing,
2009 structural_signature: StructuralSignature {
2010 dominant_drift_direction: drift_dir,
2011 peak_slew_magnitude: peak_slew,
2012 duration_windows: end - start + 1,
2013 signal_correlation: contributing as f64 / 8.0,
2014 },
2015 root_cause_signal_index: None,
2016 }
2017 }
2018
2019 #[test]
2028 fn matches_memory_leak_drift() {
2029 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2030 let ep = blank_episode_with(
2031 ReasonCode::SustainedOutwardDrift, 0.05, 1, 0, 30, DriftDirection::Positive);
2032 let got = bank.match_episode(&ep, 0.7, 0.5);
2033 match got {
2038 SemanticDisposition::Named(MotifClass::MemoryLeakDrift)
2039 | SemanticDisposition::Named(MotifClass::JvmHeapPressure)
2040 | SemanticDisposition::Named(MotifClass::ResourceSaturation)
2041 | SemanticDisposition::Named(MotifClass::DiskIoSaturation)
2042 | SemanticDisposition::Named(MotifClass::CpuSaturation)
2043 | SemanticDisposition::Named(MotifClass::PacketLossErrorEscalation)
2044 | SemanticDisposition::Named(MotifClass::ErrorRateEscalation)
2045 | SemanticDisposition::Named(MotifClass::SaturationTrending)
2046 | SemanticDisposition::Named(MotifClass::ConnectionPoolExhaustionDrift)
2047 | SemanticDisposition::Named(MotifClass::DependencySlowdown)
2048 | SemanticDisposition::Named(MotifClass::QueueBackpressure)
2049 | SemanticDisposition::Named(MotifClass::LogVolumeAnomaly)
2050 | SemanticDisposition::Named(MotifClass::LogSeverityEscalation) => {}
2051 other => panic!("expected a SustainedOutwardDrift motif, got {:?}", other),
2052 }
2053 }
2054
2055 #[test]
2056 fn cascading_timeout_requires_multi_service_correlation() {
2057 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2060 let ep = blank_episode_with(
2061 ReasonCode::AbruptSlewViolation, 0.9, 4, 10, 14, DriftDirection::Positive);
2062 let got = bank.match_episode(&ep, 0.3, 0.1);
2063 match got {
2066 SemanticDisposition::Named(MotifClass::CascadingTimeoutSlew)
2067 | SemanticDisposition::Named(MotifClass::CircuitBreakerOpenShift)
2068 | SemanticDisposition::Named(MotifClass::AuthenticationFailureSpike)
2069 | SemanticDisposition::Named(MotifClass::EpisodicTransientSpike)
2070 | SemanticDisposition::Named(MotifClass::MetricCorrelationCollapse)
2071 | SemanticDisposition::Named(MotifClass::LogTraceTemporalDecorrelation) => {}
2072 SemanticDisposition::Named(MotifClass::DeploymentRegressionSlew) => panic!(
2073 "DeploymentRegressionSlew matched on multi-service signature; \
2074 max_correlation_count gate failed"
2075 ),
2076 other => panic!("unexpected motif on cascading signature: {:?}", other),
2077 }
2078 }
2079
2080 #[test]
2081 fn deployment_regression_requires_single_service_step() {
2082 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2086 let ep = blank_episode_with(
2087 ReasonCode::AbruptSlewViolation, 1.0, 1, 100, 250, DriftDirection::Positive);
2088 let got = bank.match_episode(&ep, 0.1, 0.1);
2089 if let SemanticDisposition::Named(MotifClass::CascadingTimeoutSlew) = got {
2091 panic!("CascadingTimeoutSlew matched on single-service signature; \
2092 min_correlation_count gate failed");
2093 }
2094 }
2095
2096 #[test]
2097 fn empty_bank_yields_unknown() {
2098 let bank = HeuristicsBank::<8>::with_canonical_motifs();
2099 let ep = blank_episode_with(
2101 ReasonCode::SingleCrossing, 0.0, 1, 0, 1, DriftDirection::None);
2102 let got = bank.match_episode(&ep, 0.0, 0.0);
2103 assert_eq!(got, SemanticDisposition::Unknown);
2104 }
2105
2106 #[test]
2107 fn signal_lookup_v01_compatibility() {
2108 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2110 let got = bank.lookup(ReasonCode::AbruptSlewViolation, 0.0, 0.9);
2111 match got {
2112 SemanticDisposition::Named(_) => {}
2113 SemanticDisposition::Unknown => {
2114 panic!("signal-level lookup must surface a Named motif on AbruptSlewViolation + slew=0.9")
2115 }
2116 }
2117 }
2118
2119 #[test]
2120 fn provenance_tie_breaker_prefers_dataset_observed() {
2121 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2124 let ep = blank_episode_with(
2128 ReasonCode::AbruptSlewViolation, 0.6, 1, 0, 2, DriftDirection::Positive);
2129 let got = bank.match_episode(&ep, 0.0, 0.0);
2130 if let SemanticDisposition::Named(motif) = got {
2132 let entry = bank.entry_for(motif).expect("matched motif should be in bank");
2133 assert_ne!(entry.provenance, Provenance::FieldValidated,
2134 "no FieldValidated entries are populated yet");
2135 } else {
2136 panic!("expected a Named motif, got Unknown");
2137 }
2138 }
2139
2140 #[test]
2141 fn lookup_admissible_silent() {
2142 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2143 let got = bank.lookup(ReasonCode::Admissible, 0.0, 0.0);
2144 assert_eq!(got, SemanticDisposition::Unknown,
2145 "Admissible reason code has no motif; must be Unknown");
2146 }
2147
2148 #[test]
2149 fn count_after_canonical_init() {
2150 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2154 assert_eq!(bank.count(), 32);
2155 }
2156
2157 #[test]
2158 fn entry_for_returns_metadata() {
2159 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2160 let entry = bank.entry_for(MotifClass::CascadingTimeoutSlew)
2161 .expect("CascadingTimeoutSlew must be in the canonical bank");
2162 assert_eq!(entry.provenance, Provenance::DatasetObserved);
2163 assert!(!entry.dashboard_hint.is_empty());
2164 assert!(!entry.taxonomy_ref.is_empty());
2165 assert_eq!(entry.evidence_dataset, "tadbench_trainticket_F04");
2166 }
2167
2168 #[test]
2169 fn db_lock_wants_multiple_services() {
2170 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2173 let ep = blank_episode_with(
2174 ReasonCode::SustainedOutwardDrift, 0.3, 1, 0, 10, DriftDirection::Positive);
2175 let got = bank.match_episode(&ep, 0.6, 0.5);
2176 if let SemanticDisposition::Named(MotifClass::DatabaseLockContention) = got {
2177 panic!("DatabaseLockContention should not match single-service episodes");
2178 }
2179 }
2180
2181 #[test]
2182 fn transient_spike_excludes_long_duration() {
2183 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2186 let ep = blank_episode_with(
2187 ReasonCode::AbruptSlewViolation, 0.9, 2, 0, 49, DriftDirection::Positive);
2188 let got = bank.match_episode(&ep, 0.0, 0.0);
2189 if let SemanticDisposition::Named(MotifClass::EpisodicTransientSpike) = got {
2190 panic!("EpisodicTransientSpike must not match long-duration episodes");
2191 }
2192 }
2193
2194 #[test]
2195 fn jvm_gc_pause_caps_correlation() {
2196 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2199 let ep = blank_episode_with(
2200 ReasonCode::AbruptSlewViolation, 0.6, 8, 0, 5, DriftDirection::Positive);
2201 let got = bank.match_episode(&ep, 0.1, 0.4);
2202 if let SemanticDisposition::Named(MotifClass::JvmGcPause) = got {
2203 panic!("JvmGcPause must not match wide multi-service episodes");
2204 }
2205 }
2206
2207 #[test]
2208 fn high_dim_cluster_requires_many_signals() {
2209 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2211 let ep_few = blank_episode_with(
2212 ReasonCode::SustainedOutwardDrift, 0.3, 3, 0, 10, DriftDirection::Positive);
2213 let got = bank.match_episode(&ep_few, 0.5, 0.4);
2214 if let SemanticDisposition::Named(MotifClass::HighDimAnomalyCluster) = got {
2215 panic!("HighDimAnomalyCluster must not match low-correlation episodes");
2216 }
2217 }
2218
2219 #[test]
2220 fn taxonomy_ref_present_on_every_entry() {
2221 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2222 let mut i = 0;
2223 while i < bank.count {
2224 if let Some(entry) = &bank.entries[i] {
2225 assert!(!entry.taxonomy_ref.is_empty(),
2226 "entry index {} has empty taxonomy_ref", i);
2227 assert!(!entry.dashboard_hint.is_empty(),
2228 "entry index {} has empty dashboard_hint", i);
2229 assert!(!entry.evidence_dataset.is_empty(),
2230 "entry index {} has empty evidence_dataset", i);
2231 }
2232 i += 1;
2233 }
2234 }
2235
2236 #[test]
2237 fn confidence_margin_in_unit_interval() {
2238 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2239 let ep = blank_episode_with(
2240 ReasonCode::AbruptSlewViolation, 0.9, 4, 10, 14, DriftDirection::Positive);
2241 let conf = bank.match_episode_with_confidence(&ep, 0.3, 0.1);
2242 assert!(matches!(conf.disposition, SemanticDisposition::Named(_)),
2243 "high-slew multi-service episode should produce a named disposition");
2244 assert!(conf.margin >= 0.0 && conf.margin <= 1.0,
2245 "margin must be in [0, 1]; got {}", conf.margin);
2246 assert!(conf.top_score > 0.0, "top_score must be positive when match is Named");
2247 }
2248
2249 #[test]
2250 fn confidence_zero_when_unknown() {
2251 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2252 let ep = blank_episode_with(
2254 ReasonCode::SingleCrossing, 0.0, 1, 0, 1, DriftDirection::None);
2255 let conf = bank.match_episode_with_confidence(&ep, 0.0, 0.0);
2256 assert_eq!(conf.disposition, SemanticDisposition::Unknown);
2257 assert_eq!(conf.top_score, 0.0);
2258 assert_eq!(conf.margin, 0.0);
2259 }
2260
2261 #[test]
2262 fn confidence_runner_up_distinct_from_top_when_present() {
2263 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2264 let ep = blank_episode_with(
2267 ReasonCode::SustainedOutwardDrift, 0.05, 3, 0, 30, DriftDirection::Positive);
2268 let conf = bank.match_episode_with_confidence(&ep, 0.7, 0.5);
2269 if let SemanticDisposition::Named(top) = conf.disposition {
2270 if let Some(runner_up) = conf.runner_up_motif {
2271 assert_ne!(top, runner_up,
2272 "runner-up must differ from top when both are present");
2273 }
2274 }
2275 }
2276
2277 #[test]
2278 fn dataset_observed_entries_have_doi() {
2279 let bank = HeuristicsBank::<64>::with_canonical_motifs();
2282 let mut i = 0;
2283 while i < bank.count {
2284 if let Some(entry) = &bank.entries[i] {
2285 if entry.provenance == Provenance::DatasetObserved {
2286 assert!(!entry.evidence_dataset_doi.is_empty(),
2287 "DatasetObserved entry index {} missing DOI", i);
2288 }
2289 }
2290 i += 1;
2291 }
2292 }
2293}