Scaffold dd0c/cost: Welford baseline, anomaly scorer, governance engine, tests

- Welford online algorithm for running mean/stddev baselines
- Anomaly scorer: z-score → 0-100 mapping, property-based tests (10K runs, fast-check)
- Governance engine: 14-day auto-promotion with FP rate gate, injectable Clock
- Panic mode: defaults to active (safe) when Redis unreachable
- Tests: 12 scorer cases (incl 2x 10K property-based), 9 governance cases, 3 panic mode cases
- PostgreSQL schema with RLS: baselines (optimistic locking), anomalies, remediation_actions
- Fly.io config, Dockerfile
This commit is contained in:
2026-03-01 02:52:53 +00:00
parent 23db74b306
commit 6f692fc5ef
8 changed files with 540 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import pino from 'pino';
const logger = pino({ name: 'governance' });
/**
* Clock interface for deterministic governance tests (BMad must-have).
* Inject FakeClock in tests, RealClock in production.
*/
export interface Clock {
now(): number;
}
export class RealClock implements Clock {
now() { return Date.now(); }
}
export class FakeClock implements Clock {
private current: number;
constructor(start = Date.now()) { this.current = start; }
now() { return this.current; }
advanceBy(ms: number) { this.current += ms; }
}
export interface PromotionResult {
promoted: boolean;
newMode?: 'audit' | 'enforce';
reason?: string;
}
const PROMOTION_DAYS = 14;
const MAX_FP_RATE = 0.10; // 10% false positive rate
/**
* Governance engine — manages auto-promotion from shadow → audit → enforce.
* Uses injectable Clock for deterministic testing.
*/
export class GovernanceEngine {
private clock: Clock;
constructor(clock: Clock = new RealClock()) {
this.clock = clock;
}
/**
* Evaluate whether a tenant should be promoted to the next governance mode.
* Requires: 14+ days in current mode AND false positive rate < 10%.
*/
evaluatePromotion(
tenantId: string,
stats: { fpRate: number; daysInCurrentMode: number },
): PromotionResult {
if (stats.daysInCurrentMode < PROMOTION_DAYS) {
return { promoted: false, reason: `Only ${stats.daysInCurrentMode} days — need ${PROMOTION_DAYS}` };
}
if (stats.fpRate > MAX_FP_RATE) {
return {
promoted: false,
reason: `false-positive rate ${(stats.fpRate * 100).toFixed(1)}% exceeds ${MAX_FP_RATE * 100}% threshold`,
};
}
return { promoted: true, newMode: 'audit' };
}
/**
* Check panic mode status.
* Defaults to panic=active (safe) when Redis is unreachable (BMad must-have).
*/
async checkPanicMode(tenantId: string, redis: any): Promise<boolean> {
try {
const val = await redis.get(`panic:${tenantId}`);
return val === '1';
} catch (err) {
logger.warn({ tenantId, error: (err as Error).message },
'Redis unreachable — defaulting to panic=active');
return true; // Safe default: panic IS active
}
}
}