|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- # 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**:
-
- ```typescript
- // 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**:
-
- ```typescript
- // 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**:
-
- ```typescript
- // 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**:
-
- ```typescript
- // 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_
|