Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
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}