Files
rust_browser/_bmad/tea/testarch/knowledge/probability-impact.md
Zachary D. Rowitsch 931f17b70e
All checks were successful
ci / fast (linux) (push) Successful in 6m46s
Add BMAD framework, planning artifacts, and architecture decision document
Install BMAD workflow framework with agent commands and templates.
Create product brief, PRD, project context, and architecture decision
document covering networking/persistence strategy, JS engine evolution
path, threading model, web_api scaling, system integration, and
tab/process model. Add generated project documentation (architecture
overview, component inventory, development guide, source tree analysis).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:13:06 -04:00

19 KiB
Raw Permalink Blame History

Probability and Impact Scale

Principle

Risk scoring uses a probability × impact matrix (1-9 scale) to prioritize testing efforts. Higher scores (6-9) demand immediate action; lower scores (1-3) require documentation only. This systematic approach ensures testing resources focus on the highest-value risks.

Rationale

The Problem: Without quantifiable risk assessment, teams over-test low-value scenarios while missing critical risks. Gut feeling leads to inconsistent prioritization and missed edge cases.

The Solution: Standardize risk evaluation with a 3×3 matrix (probability: 1-3, impact: 1-3). Multiply to derive risk score (1-9). Automate classification (DOCUMENT, MONITOR, MITIGATE, BLOCK) based on thresholds. This approach surfaces hidden risks early and justifies testing decisions to stakeholders.

Why This Matters:

  • Consistent risk language across product, engineering, and QA
  • Objective prioritization of test scenarios (not politics)
  • Automatic gate decisions (score=9 → FAIL until resolved)
  • Audit trail for compliance and retrospectives

Pattern Examples

Example 1: Probability-Impact Matrix Implementation (Automated Classification)

Context: Implement a reusable risk scoring system with automatic threshold classification

Implementation:

// src/testing/risk-matrix.ts

/**
 * Probability levels:
 * 1 = Unlikely (standard implementation, low uncertainty)
 * 2 = Possible (edge cases or partial unknowns)
 * 3 = Likely (known issues, new integrations, high ambiguity)
 */
export type Probability = 1 | 2 | 3;

/**
 * Impact levels:
 * 1 = Minor (cosmetic issues or easy workarounds)
 * 2 = Degraded (partial feature loss or manual workaround)
 * 3 = Critical (blockers, data/security/regulatory exposure)
 */
export type Impact = 1 | 2 | 3;

/**
 * Risk score (probability × impact): 1-9
 */
export type RiskScore = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;

/**
 * Action categories based on risk score thresholds
 */
export type RiskAction = 'DOCUMENT' | 'MONITOR' | 'MITIGATE' | 'BLOCK';

export type RiskAssessment = {
  probability: Probability;
  impact: Impact;
  score: RiskScore;
  action: RiskAction;
  reasoning: string;
};

/**
 * Calculate risk score: probability × impact
 */
export function calculateRiskScore(probability: Probability, impact: Impact): RiskScore {
  return (probability * impact) as RiskScore;
}

/**
 * Classify risk action based on score thresholds:
 * - 1-3: DOCUMENT (awareness only)
 * - 4-5: MONITOR (watch closely, plan mitigations)
 * - 6-8: MITIGATE (CONCERNS at gate until mitigated)
 * - 9: BLOCK (automatic FAIL until resolved or waived)
 */
export function classifyRiskAction(score: RiskScore): RiskAction {
  if (score >= 9) return 'BLOCK';
  if (score >= 6) return 'MITIGATE';
  if (score >= 4) return 'MONITOR';
  return 'DOCUMENT';
}

/**
 * Full risk assessment with automatic classification
 */
export function assessRisk(params: { probability: Probability; impact: Impact; reasoning: string }): RiskAssessment {
  const { probability, impact, reasoning } = params;

  const score = calculateRiskScore(probability, impact);
  const action = classifyRiskAction(score);

  return { probability, impact, score, action, reasoning };
}

/**
 * Generate risk matrix visualization (3x3 grid)
 * Returns markdown table with color-coded scores
 */
export function generateRiskMatrix(): string {
  const matrix: string[][] = [];
  const header = ['Impact \\ Probability', 'Unlikely (1)', 'Possible (2)', 'Likely (3)'];
  matrix.push(header);

  const impactLabels = ['Critical (3)', 'Degraded (2)', 'Minor (1)'];
  for (let impact = 3; impact >= 1; impact--) {
    const row = [impactLabels[3 - impact]];
    for (let probability = 1; probability <= 3; probability++) {
      const score = calculateRiskScore(probability as Probability, impact as Impact);
      const action = classifyRiskAction(score);
      const emoji = action === 'BLOCK' ? '🔴' : action === 'MITIGATE' ? '🟠' : action === 'MONITOR' ? '🟡' : '🟢';
      row.push(`${emoji} ${score}`);
    }
    matrix.push(row);
  }

  return matrix.map((row) => `| ${row.join(' | ')} |`).join('\n');
}

Key Points:

  • Type-safe probability/impact (1-3 enforced at compile time)
  • Automatic action classification (DOCUMENT, MONITOR, MITIGATE, BLOCK)
  • Visual matrix generation for documentation
  • Risk score formula: probability * impact (max = 9)
  • Threshold-based decision rules (6-8 = MITIGATE, 9 = BLOCK)

Example 2: Risk Assessment Workflow (Test Planning Integration)

Context: Apply risk matrix during test design to prioritize scenarios

Implementation:

// tests/e2e/test-planning/risk-assessment.ts
import { assessRisk, generateRiskMatrix, type RiskAssessment } from '../../../src/testing/risk-matrix';

export type TestScenario = {
  id: string;
  title: string;
  feature: string;
  risk: RiskAssessment;
  testLevel: 'E2E' | 'API' | 'Unit';
  priority: 'P0' | 'P1' | 'P2' | 'P3';
  owner: string;
};

/**
 * Assess test scenarios and auto-assign priority based on risk score
 */
export function assessTestScenarios(scenarios: Omit<TestScenario, 'risk' | 'priority'>[]): TestScenario[] {
  return scenarios.map((scenario) => {
    // Auto-assign priority based on risk score
    const priority = mapRiskToPriority(scenario.risk.score);
    return { ...scenario, priority };
  });
}

/**
 * Map risk score to test priority (P0-P3)
 * P0: Critical (score 9) - blocks release
 * P1: High (score 6-8) - must fix before release
 * P2: Medium (score 4-5) - fix if time permits
 * P3: Low (score 1-3) - document and defer
 */
function mapRiskToPriority(score: number): 'P0' | 'P1' | 'P2' | 'P3' {
  if (score === 9) return 'P0';
  if (score >= 6) return 'P1';
  if (score >= 4) return 'P2';
  return 'P3';
}

/**
 * Example: Payment flow risk assessment
 */
export const paymentScenarios: Array<Omit<TestScenario, 'priority'>> = [
  {
    id: 'PAY-001',
    title: 'Valid credit card payment completes successfully',
    feature: 'Checkout',
    risk: assessRisk({
      probability: 2, // Possible (standard Stripe integration)
      impact: 3, // Critical (revenue loss if broken)
      reasoning: 'Core revenue flow, but Stripe is well-tested',
    }),
    testLevel: 'E2E',
    owner: 'qa-team',
  },
  {
    id: 'PAY-002',
    title: 'Expired credit card shows user-friendly error',
    feature: 'Checkout',
    risk: assessRisk({
      probability: 3, // Likely (edge case handling often buggy)
      impact: 2, // Degraded (users see error, but can retry)
      reasoning: 'Error handling logic is custom and complex',
    }),
    testLevel: 'E2E',
    owner: 'qa-team',
  },
  {
    id: 'PAY-003',
    title: 'Payment confirmation email formatting is correct',
    feature: 'Email',
    risk: assessRisk({
      probability: 2, // Possible (template changes occasionally break)
      impact: 1, // Minor (cosmetic issue, email still sent)
      reasoning: 'Non-blocking, users get email regardless',
    }),
    testLevel: 'Unit',
    owner: 'dev-team',
  },
  {
    id: 'PAY-004',
    title: 'Payment fails gracefully when Stripe is down',
    feature: 'Checkout',
    risk: assessRisk({
      probability: 1, // Unlikely (Stripe has 99.99% uptime)
      impact: 3, // Critical (complete checkout failure)
      reasoning: 'Rare but catastrophic, requires retry mechanism',
    }),
    testLevel: 'API',
    owner: 'qa-team',
  },
];

/**
 * Generate risk assessment report with priority distribution
 */
export function generateRiskReport(scenarios: TestScenario[]): string {
  const priorityCounts = scenarios.reduce(
    (acc, s) => {
      acc[s.priority] = (acc[s.priority] || 0) + 1;
      return acc;
    },
    {} as Record<string, number>,
  );

  const actionCounts = scenarios.reduce(
    (acc, s) => {
      acc[s.risk.action] = (acc[s.risk.action] || 0) + 1;
      return acc;
    },
    {} as Record<string, number>,
  );

  return `
# Risk Assessment Report

## Risk Matrix
${generateRiskMatrix()}

## Priority Distribution
- **P0 (Blocker)**: ${priorityCounts.P0 || 0} scenarios
- **P1 (High)**: ${priorityCounts.P1 || 0} scenarios
- **P2 (Medium)**: ${priorityCounts.P2 || 0} scenarios
- **P3 (Low)**: ${priorityCounts.P3 || 0} scenarios

## Action Required
- **BLOCK**: ${actionCounts.BLOCK || 0} scenarios (auto-fail gate)
- **MITIGATE**: ${actionCounts.MITIGATE || 0} scenarios (concerns at gate)
- **MONITOR**: ${actionCounts.MONITOR || 0} scenarios (watch closely)
- **DOCUMENT**: ${actionCounts.DOCUMENT || 0} scenarios (awareness only)

## Scenarios by Risk Score (Highest First)
${scenarios
  .sort((a, b) => b.risk.score - a.risk.score)
  .map((s) => `- **[${s.priority}]** ${s.id}: ${s.title} (Score: ${s.risk.score} - ${s.risk.action})`)
  .join('\n')}
`.trim();
}

Key Points:

  • Risk score → Priority mapping (P0-P3 automated)
  • Report generation with priority/action distribution
  • Scenarios sorted by risk score (highest first)
  • Visual matrix included in reports
  • Reusable across projects (extract to shared library)

Example 3: Dynamic Risk Re-Assessment (Continuous Evaluation)

Context: Recalculate risk scores as project evolves (requirements change, mitigations implemented)

Implementation:

// src/testing/risk-tracking.ts
import { type RiskAssessment, assessRisk, type Probability, type Impact } from './risk-matrix';

export type RiskHistory = {
  timestamp: Date;
  assessment: RiskAssessment;
  changedBy: string;
  reason: string;
};

export type TrackedRisk = {
  id: string;
  title: string;
  feature: string;
  currentRisk: RiskAssessment;
  history: RiskHistory[];
  mitigations: string[];
  status: 'OPEN' | 'MITIGATED' | 'WAIVED' | 'RESOLVED';
};

export class RiskTracker {
  private risks: Map<string, TrackedRisk> = new Map();

  /**
   * Add new risk to tracker
   */
  addRisk(params: {
    id: string;
    title: string;
    feature: string;
    probability: Probability;
    impact: Impact;
    reasoning: string;
    changedBy: string;
  }): TrackedRisk {
    const { id, title, feature, probability, impact, reasoning, changedBy } = params;

    const assessment = assessRisk({ probability, impact, reasoning });

    const risk: TrackedRisk = {
      id,
      title,
      feature,
      currentRisk: assessment,
      history: [
        {
          timestamp: new Date(),
          assessment,
          changedBy,
          reason: 'Initial assessment',
        },
      ],
      mitigations: [],
      status: 'OPEN',
    };

    this.risks.set(id, risk);
    return risk;
  }

  /**
   * Reassess risk (probability or impact changed)
   */
  reassessRisk(params: {
    id: string;
    probability?: Probability;
    impact?: Impact;
    reasoning: string;
    changedBy: string;
  }): TrackedRisk | null {
    const { id, probability, impact, reasoning, changedBy } = params;
    const risk = this.risks.get(id);
    if (!risk) return null;

    // Use existing values if not provided
    const newProbability = probability ?? risk.currentRisk.probability;
    const newImpact = impact ?? risk.currentRisk.impact;

    const newAssessment = assessRisk({
      probability: newProbability,
      impact: newImpact,
      reasoning,
    });

    risk.currentRisk = newAssessment;
    risk.history.push({
      timestamp: new Date(),
      assessment: newAssessment,
      changedBy,
      reason: reasoning,
    });

    this.risks.set(id, risk);
    return risk;
  }

  /**
   * Mark risk as mitigated (probability reduced)
   */
  mitigateRisk(params: { id: string; newProbability: Probability; mitigation: string; changedBy: string }): TrackedRisk | null {
    const { id, newProbability, mitigation, changedBy } = params;
    const risk = this.reassessRisk({
      id,
      probability: newProbability,
      reasoning: `Mitigation implemented: ${mitigation}`,
      changedBy,
    });

    if (risk) {
      risk.mitigations.push(mitigation);
      if (risk.currentRisk.action === 'DOCUMENT' || risk.currentRisk.action === 'MONITOR') {
        risk.status = 'MITIGATED';
      }
    }

    return risk;
  }

  /**
   * Get risks requiring action (MITIGATE or BLOCK)
   */
  getRisksRequiringAction(): TrackedRisk[] {
    return Array.from(this.risks.values()).filter(
      (r) => r.status === 'OPEN' && (r.currentRisk.action === 'MITIGATE' || r.currentRisk.action === 'BLOCK'),
    );
  }

  /**
   * Generate risk trend report (show changes over time)
   */
  generateTrendReport(riskId: string): string | null {
    const risk = this.risks.get(riskId);
    if (!risk) return null;

    return `
# Risk Trend Report: ${risk.id}

**Title**: ${risk.title}
**Feature**: ${risk.feature}
**Status**: ${risk.status}

## Current Assessment
- **Probability**: ${risk.currentRisk.probability}
- **Impact**: ${risk.currentRisk.impact}
- **Score**: ${risk.currentRisk.score}
- **Action**: ${risk.currentRisk.action}
- **Reasoning**: ${risk.currentRisk.reasoning}

## Mitigations Applied
${risk.mitigations.length > 0 ? risk.mitigations.map((m) => `- ${m}`).join('\n') : '- None'}

## History (${risk.history.length} changes)
${risk.history
  .reverse()
  .map((h) => `- **${h.timestamp.toISOString()}** by ${h.changedBy}: Score ${h.assessment.score} (${h.assessment.action}) - ${h.reason}`)
  .join('\n')}
`.trim();
  }
}

Key Points:

  • Historical tracking (audit trail for risk changes)
  • Mitigation impact tracking (probability reduction)
  • Status lifecycle (OPEN → MITIGATED → RESOLVED)
  • Trend reports (show risk evolution over time)
  • Re-assessment triggers (requirements change, new info)

Example 4: Risk Matrix in Gate Decision (Integration with Trace Workflow)

Context: Use probability-impact scores to drive gate decisions (PASS/CONCERNS/FAIL/WAIVED)

Implementation:

// src/testing/gate-decision.ts
import { type RiskScore, classifyRiskAction, type RiskAction } from './risk-matrix';
import { type TrackedRisk } from './risk-tracking';

export type GateDecision = 'PASS' | 'CONCERNS' | 'FAIL' | 'WAIVED';

export type GateResult = {
  decision: GateDecision;
  blockers: TrackedRisk[]; // Score=9, action=BLOCK
  concerns: TrackedRisk[]; // Score 6-8, action=MITIGATE
  monitored: TrackedRisk[]; // Score 4-5, action=MONITOR
  documented: TrackedRisk[]; // Score 1-3, action=DOCUMENT
  summary: string;
};

/**
 * Evaluate gate based on risk assessments
 */
export function evaluateGateFromRisks(risks: TrackedRisk[]): GateResult {
  const blockers = risks.filter((r) => r.currentRisk.action === 'BLOCK' && r.status === 'OPEN');
  const concerns = risks.filter((r) => r.currentRisk.action === 'MITIGATE' && r.status === 'OPEN');
  const monitored = risks.filter((r) => r.currentRisk.action === 'MONITOR');
  const documented = risks.filter((r) => r.currentRisk.action === 'DOCUMENT');

  let decision: GateDecision;

  if (blockers.length > 0) {
    decision = 'FAIL';
  } else if (concerns.length > 0) {
    decision = 'CONCERNS';
  } else {
    decision = 'PASS';
  }

  const summary = generateGateSummary({ decision, blockers, concerns, monitored, documented });

  return { decision, blockers, concerns, monitored, documented, summary };
}

/**
 * Generate gate decision summary
 */
function generateGateSummary(result: Omit<GateResult, 'summary'>): string {
  const { decision, blockers, concerns, monitored, documented } = result;

  const lines: string[] = [`## Gate Decision: ${decision}`];

  if (decision === 'FAIL') {
    lines.push(`\n**Blockers** (${blockers.length}): Automatic FAIL until resolved or waived`);
    blockers.forEach((r) => {
      lines.push(`- **${r.id}**: ${r.title} (Score: ${r.currentRisk.score})`);
      lines.push(`  - Probability: ${r.currentRisk.probability}, Impact: ${r.currentRisk.impact}`);
      lines.push(`  - Reasoning: ${r.currentRisk.reasoning}`);
    });
  }

  if (concerns.length > 0) {
    lines.push(`\n**Concerns** (${concerns.length}): Address before release`);
    concerns.forEach((r) => {
      lines.push(`- **${r.id}**: ${r.title} (Score: ${r.currentRisk.score})`);
      lines.push(`  - Mitigations: ${r.mitigations.join(', ') || 'None'}`);
    });
  }

  if (monitored.length > 0) {
    lines.push(`\n**Monitored** (${monitored.length}): Watch closely`);
    monitored.forEach((r) => lines.push(`- **${r.id}**: ${r.title} (Score: ${r.currentRisk.score})`));
  }

  if (documented.length > 0) {
    lines.push(`\n**Documented** (${documented.length}): Awareness only`);
  }

  lines.push(`\n---\n`);
  lines.push(`**Next Steps**:`);
  if (decision === 'FAIL') {
    lines.push(`- Resolve blockers or request formal waiver`);
  } else if (decision === 'CONCERNS') {
    lines.push(`- Implement mitigations for high-risk scenarios (score 6-8)`);
    lines.push(`- Re-run gate after mitigations`);
  } else {
    lines.push(`- Proceed with release`);
  }

  return lines.join('\n');
}

Key Points:

  • Gate decision driven by risk scores (not gut feeling)
  • Automatic FAIL for score=9 (blockers)
  • CONCERNS for score 6-8 (requires mitigation)
  • PASS only when no blockers/concerns
  • Actionable summary with next steps
  • Integration with trace workflow (Phase 2)

Probability-Impact Threshold Summary

Score Action Gate Impact Typical Use Case
1-3 DOCUMENT None Cosmetic issues, low-priority bugs
4-5 MONITOR None (watch closely) Edge cases, partial unknowns
6-8 MITIGATE CONCERNS at gate High-impact scenarios needing coverage
9 BLOCK Automatic FAIL Critical blockers, must resolve

Risk Assessment Checklist

Before deploying risk matrix:

  • Probability scale defined: 1 (unlikely), 2 (possible), 3 (likely) with clear examples
  • Impact scale defined: 1 (minor), 2 (degraded), 3 (critical) with concrete criteria
  • Threshold rules documented: Score → Action mapping (1-3 = DOCUMENT, 4-5 = MONITOR, 6-8 = MITIGATE, 9 = BLOCK)
  • Gate integration: Risk scores drive gate decisions (PASS/CONCERNS/FAIL/WAIVED)
  • Re-assessment process: Risks re-evaluated as project evolves (requirements change, mitigations applied)
  • Audit trail: Historical tracking for risk changes (who, when, why)
  • Mitigation tracking: Link mitigations to probability reduction (quantify impact)
  • Reporting: Risk matrix visualization, trend reports, gate summaries

Integration Points

  • Used in workflows: *test-design (initial risk assessment), *trace (gate decision Phase 2), *nfr-assess (security/performance risks)
  • Related fragments: risk-governance.md (risk scoring matrix, gate decision engine), test-priorities-matrix.md (P0-P3 mapping), nfr-criteria.md (impact assessment for NFRs)
  • Tools: TypeScript for type safety, markdown for reports, version control for audit trail

Source: Murat risk model summary, gate decision patterns from production systems, probability-impact matrix from risk governance practices