Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
at main 445 lines 15 kB view raw
1/** 2 * Replacement Audit — the diagnostic from Chapter 4 of The Phoenix Architecture. 3 * 4 * "Pick a component and ask: could I replace this implementation entirely 5 * and have its dependents not notice?" 6 * 7 * Assesses each IU on: 8 * 1. Boundary clarity — are contracts explicit and complete? 9 * 2. Evaluation coverage — can a replacement be verified? 10 * 3. Blast radius — how many dependents break if replacement goes wrong? 11 * 4. Deletion safety — can it be removed without uncontrolled failure? 12 * 5. Pace layer appropriateness — is regeneration cadence correct? 13 * 6. Conceptual mass — is cognitive burden within budget? 14 * 7. Negative knowledge — are past failures consulted? 15 */ 16 17import type { ImplementationUnit } from './models/iu.js'; 18import type { EvaluationCoverage } from './models/evaluation.js'; 19import type { ConceptualMassReport } from './models/conceptual-mass.js'; 20import type { PaceLayerMetadata } from './models/pace-layer.js'; 21import type { NegativeKnowledge } from './models/negative-knowledge.js'; 22import { 23 computeConceptualMass, 24 interactionPotential, 25 checkRatchet, 26 MASS_THRESHOLDS, 27} from './models/conceptual-mass.js'; 28 29/** 30 * Readiness gradient — from The Phoenix Architecture Chapter 21. 31 * 32 * opaque → behavior unknown, deeply coupled 33 * observable → behavior documented, boundaries traced 34 * evaluable → evaluations capture real behavior 35 * regenerable → safe to delete and replace 36 */ 37export type ReadinessLevel = 'opaque' | 'observable' | 'evaluable' | 'regenerable'; 38 39export interface AuditResult { 40 iu_id: string; 41 iu_name: string; 42 readiness: ReadinessLevel; 43 score: number; // 0-100 44 boundary_clarity: AuditDimension; 45 evaluation_coverage: AuditDimension; 46 blast_radius: AuditDimension; 47 deletion_safety: AuditDimension; 48 pace_layer: AuditDimension; 49 conceptual_mass: AuditDimension; 50 negative_knowledge: AuditDimension; 51 blockers: AuditBlocker[]; 52 recommendations: string[]; 53} 54 55export interface AuditDimension { 56 name: string; 57 score: number; // 0-100 58 status: 'good' | 'warning' | 'critical'; 59 detail: string; 60} 61 62export interface AuditBlocker { 63 category: 'boundary' | 'evaluation' | 'coupling' | 'mass' | 'pace' | 'negative_knowledge'; 64 severity: 'error' | 'warning'; 65 message: string; 66 recommended_action: string; 67} 68 69export interface AuditInput { 70 iu: ImplementationUnit; 71 allIUs: ImplementationUnit[]; 72 evalCoverage: EvaluationCoverage; 73 paceLayer?: PaceLayerMetadata; 74 negativeKnowledge: NegativeKnowledge[]; 75 previousMass?: number; 76} 77 78/** 79 * Run the replacement audit on a single IU. 80 */ 81export function auditIU(input: AuditInput): AuditResult { 82 const { iu, allIUs, evalCoverage, paceLayer, negativeKnowledge, previousMass } = input; 83 const blockers: AuditBlocker[] = []; 84 const recommendations: string[] = []; 85 86 // 1. Boundary clarity 87 const boundaryClarity = assessBoundaryClarity(iu, blockers); 88 89 // 2. Evaluation coverage 90 const evalDimension = assessEvaluationCoverage(iu, evalCoverage, blockers); 91 92 // 3. Blast radius 93 const blastRadius = assessBlastRadius(iu, allIUs, blockers); 94 95 // 4. Deletion safety (composite of boundary + eval + blast radius) 96 const deletionSafety = assessDeletionSafety(boundaryClarity, evalDimension, blastRadius); 97 98 // 5. Pace layer 99 const paceDimension = assessPaceLayer(iu, paceLayer, blockers); 100 101 // 6. Conceptual mass 102 const massDimension = assessConceptualMass(iu, previousMass, blockers); 103 104 // 7. Negative knowledge 105 const nkDimension = assessNegativeKnowledge(iu, negativeKnowledge, blockers, recommendations); 106 107 // Composite score (weighted) 108 const score = Math.round( 109 boundaryClarity.score * 0.20 + 110 evalDimension.score * 0.25 + 111 blastRadius.score * 0.15 + 112 deletionSafety.score * 0.15 + 113 paceDimension.score * 0.10 + 114 massDimension.score * 0.10 + 115 nkDimension.score * 0.05 116 ); 117 118 // Readiness level 119 const readiness = scoreToReadiness(score, blockers); 120 121 // Generate recommendations 122 if (evalCoverage.gaps.length > 0) { 123 recommendations.push(`Address ${evalCoverage.gaps.length} evaluation gap(s) before regenerating`); 124 } 125 if (boundaryClarity.score < 50) { 126 recommendations.push('Define explicit boundary contracts before attempting regeneration'); 127 } 128 if (blastRadius.score < 50) { 129 recommendations.push('Reduce blast radius by introducing interface boundaries with dependents'); 130 } 131 132 return { 133 iu_id: iu.iu_id, 134 iu_name: iu.name, 135 readiness, 136 score, 137 boundary_clarity: boundaryClarity, 138 evaluation_coverage: evalDimension, 139 blast_radius: blastRadius, 140 deletion_safety: deletionSafety, 141 pace_layer: paceDimension, 142 conceptual_mass: massDimension, 143 negative_knowledge: nkDimension, 144 blockers, 145 recommendations, 146 }; 147} 148 149/** 150 * Audit all IUs in the system. 151 */ 152export function auditAll( 153 ius: ImplementationUnit[], 154 evalCoverages: Map<string, EvaluationCoverage>, 155 paceLayers: Map<string, PaceLayerMetadata>, 156 negativeKnowledge: NegativeKnowledge[], 157 previousMasses: Map<string, number>, 158): AuditResult[] { 159 return ius.map(iu => auditIU({ 160 iu, 161 allIUs: ius, 162 evalCoverage: evalCoverages.get(iu.iu_id) ?? emptyEvalCoverage(iu), 163 paceLayer: paceLayers.get(iu.iu_id), 164 negativeKnowledge: negativeKnowledge.filter(nk => nk.subject_id === iu.iu_id), 165 previousMass: previousMasses.get(iu.iu_id), 166 })); 167} 168 169// ─── Dimension Assessors ───────────────────────────────────────────────────── 170 171function assessBoundaryClarity(iu: ImplementationUnit, blockers: AuditBlocker[]): AuditDimension { 172 let score = 0; 173 const bp = iu.boundary_policy; 174 const contract = iu.contract; 175 176 // Contract completeness 177 if (contract.description.length > 0) score += 15; 178 if (contract.inputs.length > 0) score += 20; 179 if (contract.outputs.length > 0) score += 20; 180 if (contract.invariants.length > 0) score += 15; 181 182 // Boundary policy declared 183 const hasAllowedIUs = bp.code.allowed_ius.length > 0; 184 const hasForbiddenIUs = bp.code.forbidden_ius.length > 0 || bp.code.forbidden_packages.length > 0; 185 if (hasAllowedIUs || hasForbiddenIUs) score += 15; 186 187 // Side channels declared 188 const sideChannels = Object.values(bp.side_channels).flat(); 189 if (sideChannels.length > 0) score += 15; 190 191 if (score < 40) { 192 blockers.push({ 193 category: 'boundary', 194 severity: 'error', 195 message: `${iu.name} has weak boundary definition (score: ${score}/100)`, 196 recommended_action: 'Define explicit inputs, outputs, invariants, and boundary policy', 197 }); 198 } 199 200 return { 201 name: 'Boundary Clarity', 202 score: Math.min(score, 100), 203 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 204 detail: `Contract: ${contract.inputs.length} inputs, ${contract.outputs.length} outputs, ${contract.invariants.length} invariants`, 205 }; 206} 207 208function assessEvaluationCoverage( 209 iu: ImplementationUnit, 210 coverage: EvaluationCoverage, 211 blockers: AuditBlocker[], 212): AuditDimension { 213 let score = Math.round(coverage.coverage_ratio * 60); 214 215 // Bonus for diversity of evaluation bindings 216 const bindingCount = Object.values(coverage.by_binding).filter(v => v > 0).length; 217 score += bindingCount * 8; 218 219 // Penalty for gaps 220 score -= coverage.gaps.length * 5; 221 score = Math.max(0, Math.min(100, score)); 222 223 if (coverage.total_evaluations === 0) { 224 blockers.push({ 225 category: 'evaluation', 226 severity: 'error', 227 message: `${iu.name} has no behavioral evaluations`, 228 recommended_action: 'Write evaluations at the IU boundary before regenerating', 229 }); 230 } else if (coverage.gaps.length > 2) { 231 blockers.push({ 232 category: 'evaluation', 233 severity: 'warning', 234 message: `${iu.name} has ${coverage.gaps.length} evaluation gaps`, 235 recommended_action: 'Address evaluation gaps to improve regeneration safety', 236 }); 237 } 238 239 return { 240 name: 'Evaluation Coverage', 241 score, 242 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 243 detail: `${coverage.total_evaluations} evaluations, ${Math.round(coverage.coverage_ratio * 100)}% canon coverage, ${coverage.gaps.length} gaps`, 244 }; 245} 246 247function assessBlastRadius( 248 iu: ImplementationUnit, 249 allIUs: ImplementationUnit[], 250 blockers: AuditBlocker[], 251): AuditDimension { 252 // Count how many other IUs depend on this one 253 const dependentCount = allIUs.filter(other => 254 other.iu_id !== iu.iu_id && other.dependencies.includes(iu.iu_id) 255 ).length; 256 257 // Invert: fewer dependents = higher score 258 const maxDeps = Math.max(allIUs.length - 1, 1); 259 const score = Math.round((1 - dependentCount / maxDeps) * 100); 260 261 if (dependentCount > 3) { 262 blockers.push({ 263 category: 'coupling', 264 severity: 'warning', 265 message: `${iu.name} has ${dependentCount} dependents — wide blast radius`, 266 recommended_action: 'Consider introducing interface boundaries to reduce coupling', 267 }); 268 } 269 270 return { 271 name: 'Blast Radius', 272 score, 273 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 274 detail: `${dependentCount} dependent IU(s)`, 275 }; 276} 277 278function assessDeletionSafety( 279 boundary: AuditDimension, 280 evaluation: AuditDimension, 281 blastRadius: AuditDimension, 282): AuditDimension { 283 // Deletion safety is the minimum of the three foundations 284 const score = Math.min(boundary.score, evaluation.score, blastRadius.score); 285 286 return { 287 name: 'Deletion Safety', 288 score, 289 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 290 detail: `Min of boundary (${boundary.score}), eval (${evaluation.score}), blast (${blastRadius.score})`, 291 }; 292} 293 294function assessPaceLayer( 295 iu: ImplementationUnit, 296 paceLayer: PaceLayerMetadata | undefined, 297 blockers: AuditBlocker[], 298): AuditDimension { 299 if (!paceLayer) { 300 blockers.push({ 301 category: 'pace', 302 severity: 'warning', 303 message: `${iu.name} has no pace layer classification`, 304 recommended_action: 'Classify IU into a pace layer: surface, service, domain, or foundation', 305 }); 306 return { 307 name: 'Pace Layer', 308 score: 50, 309 status: 'warning', 310 detail: 'No pace layer classification', 311 }; 312 } 313 314 let score = 70; // Classified is already good 315 if (paceLayer.classification_rationale !== 'Default classification — needs review') { 316 score += 15; // Reviewed classification 317 } 318 if (paceLayer.conservation) { 319 score += 15; // Conservation is explicitly declared 320 } 321 322 return { 323 name: 'Pace Layer', 324 score: Math.min(score, 100), 325 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 326 detail: `${paceLayer.pace_layer} layer, ${paceLayer.conservation ? 'conservation' : 'non-conservation'}, weight: ${paceLayer.dependency_weight}`, 327 }; 328} 329 330function assessConceptualMass( 331 iu: ImplementationUnit, 332 previousMass: number | undefined, 333 blockers: AuditBlocker[], 334): AuditDimension { 335 const sideChannelCount = Object.values(iu.boundary_policy.side_channels).flat().length; 336 337 const mass = computeConceptualMass({ 338 contract_inputs: iu.contract.inputs.length, 339 contract_outputs: iu.contract.outputs.length, 340 contract_invariants: iu.contract.invariants.length, 341 dependency_count: iu.dependencies.length, 342 side_channel_count: sideChannelCount, 343 canon_node_count: iu.source_canon_ids.length, 344 file_count: iu.output_files.length, 345 }); 346 347 const ip = interactionPotential(mass); 348 const ratchetViolation = checkRatchet(mass, previousMass); 349 350 // Score: lower mass = higher score 351 let score = 100; 352 if (mass > MASS_THRESHOLDS.danger) score = 20; 353 else if (mass > MASS_THRESHOLDS.warning) score = 50; 354 else if (mass > MASS_THRESHOLDS.healthy) score = 70; 355 356 if (ratchetViolation) { 357 score -= 20; 358 blockers.push({ 359 category: 'mass', 360 severity: 'warning', 361 message: `${iu.name} conceptual mass grew from ${previousMass} to ${mass} (ratchet violation)`, 362 recommended_action: 'Compact: reduce concepts, merge redundant abstractions, or split the IU', 363 }); 364 } 365 366 if (mass > MASS_THRESHOLDS.danger) { 367 blockers.push({ 368 category: 'mass', 369 severity: 'error', 370 message: `${iu.name} has conceptual mass ${mass} (>${MASS_THRESHOLDS.danger}): exceeds working memory`, 371 recommended_action: 'This IU is too complex for one person to reason about safely. Split it.', 372 }); 373 } 374 375 return { 376 name: 'Conceptual Mass', 377 score: Math.max(0, score), 378 status: score >= 70 ? 'good' : score >= 40 ? 'warning' : 'critical', 379 detail: `Mass: ${mass}, interactions: ${ip}${ratchetViolation ? ' ⚠ RATCHET VIOLATION' : ''}${previousMass !== undefined ? ` (prev: ${previousMass})` : ''}`, 380 }; 381} 382 383function assessNegativeKnowledge( 384 iu: ImplementationUnit, 385 nk: NegativeKnowledge[], 386 blockers: AuditBlocker[], 387 recommendations: string[], 388): AuditDimension { 389 // Having negative knowledge is good — it means lessons are captured 390 const score = nk.length > 0 ? 80 : 60; 391 392 if (nk.length > 0) { 393 const constraints = nk.filter(n => n.kind === 'incident_constraint'); 394 if (constraints.length > 0) { 395 recommendations.push( 396 `Consult ${constraints.length} incident constraint(s) before regenerating ${iu.name}` 397 ); 398 } 399 const failedGens = nk.filter(n => n.kind === 'failed_generation'); 400 if (failedGens.length > 0) { 401 recommendations.push( 402 `${failedGens.length} prior generation attempt(s) failed for ${iu.name} — review before retrying` 403 ); 404 } 405 } 406 407 return { 408 name: 'Negative Knowledge', 409 score, 410 status: score >= 70 ? 'good' : 'warning', 411 detail: nk.length > 0 412 ? `${nk.length} record(s): ${nk.map(n => n.kind).join(', ')}` 413 : 'No negative knowledge recorded', 414 }; 415} 416 417// ─── Helpers ───────────────────────────────────────────────────────────────── 418 419function scoreToReadiness(score: number, blockers: AuditBlocker[]): ReadinessLevel { 420 const hasErrors = blockers.some(b => b.severity === 'error'); 421 if (hasErrors || score < 30) return 'opaque'; 422 if (score < 50) return 'observable'; 423 if (score < 75) return 'evaluable'; 424 return 'regenerable'; 425} 426 427function emptyEvalCoverage(iu: ImplementationUnit): EvaluationCoverage { 428 return { 429 iu_id: iu.iu_id, 430 iu_name: iu.name, 431 total_evaluations: 0, 432 by_binding: { domain_rule: 0, boundary_contract: 0, constraint: 0, invariant: 0, failure_mode: 0 }, 433 by_origin: { specified: 0, characterization: 0, incident: 0, audit: 0 }, 434 canon_ids_covered: [], 435 canon_ids_uncovered: iu.source_canon_ids, 436 coverage_ratio: 0, 437 conservation_count: 0, 438 gaps: [{ 439 category: 'missing_boundary', 440 subject: iu.iu_id, 441 message: `No evaluations exist for ${iu.name}`, 442 recommended_action: 'Write behavioral evaluations before attempting regeneration', 443 }], 444 }; 445}