lib/closure/src/qualify/owner.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const model = @import("model.zig");
  2 const schema = @import("../schema/root.zig");
  3 const std = @import("std");
  4 
  5 const OwnerError = error{
  6     BoundRangeInvalid,
  7     CapacityPairInvalid,
  8     CellCapacityExceeded,
  9     CellDuplicate,
 10     CellInvalid,
 11     EvidenceMismatch,
 12     PlanDigestMissing,
 13     PlanEmpty,
 14     PlanRequiredMissing,
 15     PlanWorkOverflow,
 16     ResultCountMismatch,
 17     ResultOrderMismatch,
 18     SeedRangeInvalid,
 19     VerdictMismatch,
 20 };
 21 
 22 pub const Error = OwnerError;
 23 
 24 pub fn validatePlan(plan: model.Plan) Error!void {
 25     if (plan.cells.len == 0) return error.PlanEmpty;
 26     if (plan.cells.len > model.cells_max or
 27         plan.bounds.len > model.bounds_max or
 28         plan.seeds.len > model.seeds_max)
 29     {
 30         return error.CellCapacityExceeded;
 31     }
 32     if (plan.claim.isEmpty() or
 33         !plan.artifact_sha256.isKnown() or
 34         !plan.execution_profile_sha256.isKnown())
 35     {
 36         return error.PlanDigestMissing;
 37     }
 38     var required_count: u16 = 0;
 39     var work_max: u64 = 0;
 40     for (plan.cells, 0..) |*cell, index| {
 41         try validateCell(plan, cell);
 42         if (cell.requirement == .required) {
 43             required_count += 1;
 44             work_max = std.math.add(
 45                 u64,
 46                 work_max,
 47                 cell.work_max,
 48             ) catch return error.PlanWorkOverflow;
 49         }
 50         for (plan.cells[0..index]) |*prior| {
 51             if (cell.id.eql(&prior.id)) return error.CellDuplicate;
 52         }
 53     }
 54     if (required_count == 0) return error.PlanRequiredMissing;
 55     try validateCapacityPairs(plan);
 56 }
 57 
 58 pub fn evaluate(
 59     frozen: model.Frozen,
 60     rows: []const model.Row,
 61 ) Error!model.Summary {
 62     try validatePlan(frozen.plan);
 63     if (!frozen.sha256.isKnown()) return error.PlanDigestMissing;
 64     if (rows.len != frozen.plan.cells.len) {
 65         return error.ResultCountMismatch;
 66     }
 67     var summary = model.Summary{
 68         .verdict = .pass,
 69         .passed = 0,
 70         .refuted = 0,
 71         .inconclusive = 0,
 72         .not_exercised = 0,
 73         .work = 0,
 74         .high_water = 0,
 75         .allocation_count_after_seal = 0,
 76     };
 77     for (frozen.plan.cells, rows, 0..) |*cell, *row, index| {
 78         if (!cell.id.eql(&row.cell)) return error.ResultOrderMismatch;
 79         if (row.evidence != cell.evidence) return error.EvidenceMismatch;
 80         var expected = classify(cell, row);
 81         if (expected == .pass and
 82             cell.capacity_case != .none and
 83             !pairEvidenceEquivalent(
 84                 frozen.plan,
 85                 rows,
 86                 index,
 87             ))
 88         {
 89             expected = .refuted;
 90         }
 91         if (expected == .pass and
 92             cell.kind == .slo and
 93             (!row.statistics.baseline_profile_sha256.eql(
 94                 &frozen.plan.execution_profile_sha256,
 95             ) or
 96                 !row.statistics.candidate_profile_sha256.eql(
 97                     &frozen.plan.execution_profile_sha256,
 98                 )))
 99         {
100             expected = .refuted;
101         }
102         if (row.verdict != expected) return error.VerdictMismatch;
103         switch (row.verdict) {
104             .pass => summary.passed += 1,
105             .refuted => summary.refuted += 1,
106             .inconclusive => summary.inconclusive += 1,
107             .not_exercised => summary.not_exercised += 1,
108         }
109         if (cell.requirement == .required) {
110             summary.work = std.math.add(
111                 u64,
112                 summary.work,
113                 row.work,
114             ) catch std.math.maxInt(u64);
115             summary.high_water = @max(summary.high_water, row.high_water);
116             summary.allocation_count_after_seal = std.math.add(
117                 u64,
118                 summary.allocation_count_after_seal,
119                 row.allocation_count_after_seal,
120             ) catch std.math.maxInt(u64);
121         }
122     }
123     summary.verdict = if (summary.refuted != 0)
124         .refuted
125     else if (summary.inconclusive != 0)
126         .inconclusive
127     else
128         .pass;
129     return summary;
130 }
131 
132 pub fn classify(
133     cell: *const model.Cell,
134     row: *const model.Row,
135 ) schema.Verdict {
136     if (cell.requirement == .excluded) return .not_exercised;
137     if (row.capture != .complete) return .inconclusive;
138     if (cell.threshold.kind == .none) return .inconclusive;
139     if (cell.evidence == .assumption) return .inconclusive;
140     if (!evidenceComplete(cell, row)) return .refuted;
141     if (!row.contract_pass) return .refuted;
142     return switch (cell.kind) {
143         .correctness => if (cell.threshold.kind == .correctness)
144             .pass
145         else
146             .inconclusive,
147         .slo => sloVerdict(cell, row.statistics),
148     };
149 }
150 
151 fn validateCell(plan: model.Plan, cell: *const model.Cell) Error!void {
152     if (cell.id.isEmpty() or
153         cell.owner.isEmpty() or
154         cell.workload.isEmpty() or
155         cell.fault.isEmpty() or
156         cell.overload_counter.isEmpty() or
157         cell.loss_counter.isEmpty() or
158         cell.repetitions == 0 or
159         cell.work_max == 0 or
160         cell.trace_steps_max == 0)
161     {
162         return error.CellInvalid;
163     }
164     switch (cell.threshold.kind) {
165         .none, .correctness => if (cell.threshold.value != 0 or
166             cell.threshold.confidence_ppm != 0)
167         {
168             return error.CellInvalid;
169         },
170         .absolute_max, .baseline_delta_max => if (cell.threshold.confidence_ppm == 0 or
171             cell.threshold.confidence_ppm > 1_000_000)
172         {
173             return error.CellInvalid;
174         },
175     }
176     const bound_end = std.math.add(
177         usize,
178         cell.bound_start,
179         cell.bound_count,
180     ) catch return error.BoundRangeInvalid;
181     if (cell.bound_count == 0 or bound_end > plan.bounds.len) {
182         return error.BoundRangeInvalid;
183     }
184     const seed_end = std.math.add(
185         usize,
186         cell.seed_start,
187         cell.seed_count,
188     ) catch return error.SeedRangeInvalid;
189     if (cell.seed_count == 0 or seed_end > plan.seeds.len) {
190         return error.SeedRangeInvalid;
191     }
192     switch (cell.kind) {
193         .correctness => if (cell.requirement == .required and
194             cell.threshold.kind != .correctness and
195             cell.threshold.kind != .none)
196         {
197             return error.CellInvalid;
198         },
199         .slo => if (cell.requirement == .required and
200             (cell.build_mode != .release_fast or
201                 cell.evidence != .measurement or
202                 cell.repetitions < 2 or
203                 cell.threshold.kind == .correctness or
204                 (cell.threshold.kind != .none and
205                     cell.threshold.confidence_ppm == 0)))
206         {
207             return error.CellInvalid;
208         },
209     }
210     switch (cell.capacity_case) {
211         .none => {},
212         .at_capacity => if (cell.scale != cell.capacity) {
213             return error.CapacityPairInvalid;
214         },
215         .over_capacity => {
216             const over = std.math.add(
217                 u64,
218                 cell.capacity,
219                 1,
220             ) catch return error.CapacityPairInvalid;
221             if (cell.scale != over) return error.CapacityPairInvalid;
222         },
223     }
224 }
225 
226 fn validateCapacityPairs(plan: model.Plan) Error!void {
227     for (plan.cells) |*cell| {
228         if (cell.capacity_case == .none) continue;
229         const wanted: model.CapacityCase =
230             if (cell.capacity_case == .at_capacity)
231                 .over_capacity
232             else
233                 .at_capacity;
234         var matches: u8 = 0;
235         for (plan.cells) |*candidate| {
236             if (candidate.capacity_case != wanted or
237                 candidate.capacity != cell.capacity or
238                 !capacityCellsEquivalent(plan, cell, candidate))
239             {
240                 continue;
241             }
242             matches += 1;
243         }
244         if (matches != 1) return error.CapacityPairInvalid;
245     }
246 }
247 
248 fn capacityCellsEquivalent(
249     plan: model.Plan,
250     left: *const model.Cell,
251     right: *const model.Cell,
252 ) bool {
253     if (left.requirement != right.requirement or
254         left.kind != right.kind or
255         left.evidence != right.evidence or
256         left.phase != right.phase or
257         left.build_mode != right.build_mode or
258         left.repetitions != right.repetitions or
259         left.threshold.kind != right.threshold.kind or
260         left.threshold.value != right.threshold.value or
261         left.threshold.confidence_ppm !=
262             right.threshold.confidence_ppm or
263         left.work_max != right.work_max or
264         left.trace_steps_max != right.trace_steps_max or
265         !left.owner.eql(&right.owner) or
266         !left.workload.eql(&right.workload) or
267         !left.fault.eql(&right.fault) or
268         !left.overload_counter.eql(&right.overload_counter) or
269         !left.loss_counter.eql(&right.loss_counter) or
270         left.bound_count != right.bound_count or
271         left.seed_count != right.seed_count)
272     {
273         return false;
274     }
275     const left_bound_start: usize = left.bound_start;
276     const right_bound_start: usize = right.bound_start;
277     const bound_count: usize = left.bound_count;
278     for (
279         plan.bounds[left_bound_start..][0..bound_count],
280         plan.bounds[right_bound_start..][0..bound_count],
281     ) |*left_bound, *right_bound| {
282         if (left_bound.value != right_bound.value or
283             !left_bound.name.eql(&right_bound.name))
284         {
285             return false;
286         }
287     }
288     const left_seed_start: usize = left.seed_start;
289     const right_seed_start: usize = right.seed_start;
290     const seed_count: usize = left.seed_count;
291     return std.mem.eql(
292         u64,
293         plan.seeds[left_seed_start..][0..seed_count],
294         plan.seeds[right_seed_start..][0..seed_count],
295     );
296 }
297 
298 fn pairEvidenceEquivalent(
299     plan: model.Plan,
300     rows: []const model.Row,
301     index: usize,
302 ) bool {
303     const cell = &plan.cells[index];
304     const wanted: model.CapacityCase =
305         if (cell.capacity_case == .at_capacity)
306             .over_capacity
307         else
308             .at_capacity;
309     for (plan.cells, rows) |*candidate, *row| {
310         if (candidate.capacity_case != wanted or
311             candidate.capacity != cell.capacity or
312             !capacityCellsEquivalent(plan, cell, candidate))
313         {
314             continue;
315         }
316         const evidence = &rows[index];
317         return evidence.before_digest.eql(&row.before_digest) and
318             evidence.effect_before_digest.eql(
319                 &row.effect_before_digest,
320             ) and
321             evidence.storage_address_before ==
322                 row.storage_address_before and
323             evidence.storage_capacity_before ==
324                 row.storage_capacity_before and
325             evidence.overload_count_before ==
326                 row.overload_count_before and
327             evidence.loss_count_before == row.loss_count_before;
328     }
329     return false;
330 }
331 
332 fn evidenceComplete(
333     cell: *const model.Cell,
334     row: *const model.Row,
335 ) bool {
336     if (!row.before_digest.isKnown() or
337         !row.after_digest.isKnown() or
338         !row.effect_before_digest.isKnown() or
339         !row.effect_after_digest.isKnown() or
340         row.evidence_path.isEmpty() or
341         row.trace_path.isEmpty() or
342         row.allocation_count_after_seal != 0 or
343         row.storage_address_before == 0 or
344         row.storage_address_before != row.storage_address_after or
345         row.storage_capacity_before != row.storage_capacity_after or
346         row.storage_capacity_before != cell.capacity or
347         row.high_water > cell.capacity or
348         row.work > cell.work_max or
349         row.undeclared_effect_count != 0 or
350         row.undeclared_state_change_count != 0)
351     {
352         return false;
353     }
354     const accepted = std.math.add(
355         u64,
356         row.outcomes.completed,
357         row.outcomes.dropped,
358     ) catch return false;
359     const conserved = std.math.add(
360         u64,
361         accepted,
362         row.outcomes.outstanding,
363     ) catch return false;
364     const offered = std.math.add(
365         u64,
366         row.outcomes.accepted,
367         row.outcomes.rejected,
368     ) catch return false;
369     if (conserved != row.outcomes.accepted or
370         offered != row.outcomes.offered or
371         row.outcomes.offered != cell.scale)
372     {
373         return false;
374     }
375     if (!capacityTransitionComplete(cell, row)) return false;
376     return switch (cell.capacity_case) {
377         .none => row.transition == .none,
378         .at_capacity => row.transition == .none and
379             row.outcomes.accepted == row.outcomes.offered and
380             row.outcomes.completed == row.outcomes.accepted and
381             row.outcomes.rejected == 0 and
382             row.outcomes.dropped == 0 and
383             row.outcomes.outstanding == 0,
384         .over_capacity => row.transition != .none and
385             row.outcomes.outstanding == 0 and
386             row.outcomes.completed +| row.outcomes.dropped ==
387                 row.outcomes.accepted,
388     };
389 }
390 
391 fn capacityTransitionComplete(
392     cell: *const model.Cell,
393     row: *const model.Row,
394 ) bool {
395     if (cell.capacity_case != .over_capacity) {
396         return row.overload_count_before ==
397             row.overload_count_after and
398             row.loss_count_before == row.loss_count_after;
399     }
400     const overload_after = std.math.add(
401         u64,
402         row.overload_count_before,
403         1,
404     ) catch return false;
405     if (row.overload_count_after != overload_after) return false;
406     return switch (row.transition) {
407         .reject, .backpressure => row.outcomes.rejected == 1 and
408             row.outcomes.dropped == 0 and
409             row.loss_count_before == row.loss_count_after,
410         .drop, .replace => blk: {
411             const loss_after = std.math.add(
412                 u64,
413                 row.loss_count_before,
414                 1,
415             ) catch break :blk false;
416             break :blk row.outcomes.rejected == 0 and
417                 row.outcomes.dropped == 1 and
418                 row.loss_count_after == loss_after;
419         },
420         .none => false,
421     };
422 }
423 
424 fn sloVerdict(
425     cell: *const model.Cell,
426     statistics: model.Statistics,
427 ) schema.Verdict {
428     if (!statistics.present or
429         !statistics.interval_present or
430         !statistics.interleaved or
431         !statistics.baseline_profile_sha256.isKnown() or
432         !statistics.candidate_profile_sha256.isKnown() or
433         !statistics.baseline_profile_sha256.eql(
434             &statistics.candidate_profile_sha256,
435         ) or
436         statistics.sample_count < cell.repetitions or
437         statistics.baseline_count != statistics.candidate_count or
438         statistics.baseline_count == 0 or
439         @as(u64, statistics.sample_count) !=
440             @as(u64, statistics.baseline_count) +
441                 statistics.candidate_count)
442     {
443         return .inconclusive;
444     }
445     if (statistics.minimum > statistics.p50 or
446         statistics.p50 > statistics.p95 or
447         statistics.p95 > statistics.p99 or
448         statistics.p99 > statistics.maximum or
449         statistics.interval_low > statistics.interval_high)
450     {
451         return .inconclusive;
452     }
453     if (statistics.baseline_high_water > cell.capacity or
454         statistics.candidate_high_water > cell.capacity)
455     {
456         return .refuted;
457     }
458     return switch (cell.threshold.kind) {
459         .absolute_max, .baseline_delta_max => if (statistics.interval_high <= cell.threshold.value)
460             .pass
461         else
462             .refuted,
463         .none, .correctness => .inconclusive,
464     };
465 }
466 
467 fn name(value: []const u8) schema.Name {
468     return schema.Name.init(value) catch unreachable;
469 }
470 
471 fn descriptor(value: []const u8) schema.Descriptor {
472     return schema.Descriptor.init(value) catch unreachable;
473 }
474 
475 fn digest(byte: u8) schema.Digest {
476     return .{ .bytes = @splat(byte) };
477 }
478 
479 fn capacityPlan(cells: []const model.Cell) model.Plan {
480     const bounds = struct {
481         const values = [_]model.Bound{.{
482             .name = name("queue"),
483             .value = 8,
484         }};
485     }.values;
486     const seeds = struct {
487         const values = [_]u64{7};
488     }.values;
489     return .{
490         .claim = descriptor("bounded queue qualification"),
491         .artifact_sha256 = digest(1),
492         .execution_profile_sha256 = digest(2),
493         .cells = cells,
494         .bounds = &bounds,
495         .seeds = &seeds,
496     };
497 }
498 
499 fn capacityCell(
500     id: []const u8,
501     scale: u64,
502     case: model.CapacityCase,
503 ) model.Cell {
504     return .{
505         .id = name(id),
506         .owner = name("queue"),
507         .requirement = .required,
508         .kind = .correctness,
509         .evidence = .finite_exhaustive,
510         .workload = descriptor("enqueue"),
511         .phase = .validation,
512         .scale = scale,
513         .build_mode = .debug,
514         .fault = descriptor("none"),
515         .overload_counter = name("queue_overloads"),
516         .loss_counter = name("queue_losses"),
517         .bound_start = 0,
518         .bound_count = 1,
519         .seed_start = 0,
520         .seed_count = 1,
521         .repetitions = 1,
522         .threshold = .{ .kind = .correctness },
523         .capacity_case = case,
524         .capacity = 8,
525         .work_max = 100,
526         .trace_steps_max = 100,
527     };
528 }
529 
530 fn makeRow(
531     id: []const u8,
532     scale: u64,
533     transition: model.Transition,
534 ) model.Row {
535     const rejected: u64 = if (transition == .none) 0 else 1;
536     const accepted = scale - rejected;
537     return .{
538         .cell = name(id),
539         .evidence = .finite_exhaustive,
540         .verdict = .pass,
541         .capture = .complete,
542         .contract_pass = true,
543         .outcomes = .{
544             .offered = scale,
545             .accepted = accepted,
546             .completed = accepted,
547             .rejected = rejected,
548             .dropped = 0,
549             .outstanding = 0,
550         },
551         .before_digest = digest(3),
552         .after_digest = digest(4),
553         .effect_before_digest = digest(5),
554         .effect_after_digest = digest(6),
555         .high_water = accepted,
556         .work = scale,
557         .allocation_count_after_seal = 0,
558         .storage_address_before = 0x1000,
559         .storage_address_after = 0x1000,
560         .storage_capacity_before = 8,
561         .storage_capacity_after = 8,
562         .transition = transition,
563         .overload_count_before = 7,
564         .overload_count_after = if (transition == .none) 7 else 8,
565         .loss_count_before = 3,
566         .loss_count_after = 3,
567         .undeclared_effect_count = 0,
568         .undeclared_state_change_count = 0,
569         .statistics = .{},
570         .evidence_path = descriptor("evidence.jsonl"),
571         .trace_path = descriptor("trace.jsonl"),
572     };
573 }
574 
575 test "qualification evaluates paired C and C plus one evidence" {
576     const cells = [_]model.Cell{
577         capacityCell("queue-c", 8, .at_capacity),
578         capacityCell("queue-c-plus-one", 9, .over_capacity),
579     };
580     const plan = capacityPlan(&cells);
581     try validatePlan(plan);
582     const rows = [_]model.Row{
583         makeRow("queue-c", 8, .none),
584         makeRow("queue-c-plus-one", 9, .reject),
585     };
586     const summary = try evaluate(.{
587         .plan = plan,
588         .sha256 = digest(7),
589         .bytes = "{}",
590     }, &rows);
591     try std.testing.expectEqual(schema.Verdict.pass, summary.verdict);
592     try std.testing.expectEqual(@as(u16, 2), summary.passed);
593     try std.testing.expectEqual(@as(u64, 0), summary.allocation_count_after_seal);
594 }
595 
596 test "qualification forces incomplete capture to inconclusive" {
597     const cells = [_]model.Cell{
598         capacityCell("queue-c", 8, .at_capacity),
599         capacityCell("queue-c-plus-one", 9, .over_capacity),
600     };
601     const plan = capacityPlan(&cells);
602     var rows = [_]model.Row{
603         makeRow("queue-c", 8, .none),
604         makeRow("queue-c-plus-one", 9, .reject),
605     };
606     rows[1].capture = .trace_capacity_exceeded;
607     rows[1].verdict = .inconclusive;
608     const summary = try evaluate(.{
609         .plan = plan,
610         .sha256 = digest(7),
611         .bytes = "{}",
612     }, &rows);
613     try std.testing.expectEqual(
614         schema.Verdict.inconclusive,
615         summary.verdict,
616     );
617     try std.testing.expectEqual(@as(u16, 1), summary.inconclusive);
618 }
619 
620 test "qualification refutes broken conservation and overload transitions" {
621     const cell = capacityCell("queue-c-plus-one", 9, .over_capacity);
622     var evidence = makeRow("queue-c-plus-one", 9, .reject);
623     evidence.outcomes.completed = 7;
624     try std.testing.expectEqual(
625         schema.Verdict.refuted,
626         classify(&cell, &evidence),
627     );
628     evidence = makeRow("queue-c-plus-one", 9, .none);
629     try std.testing.expectEqual(
630         schema.Verdict.refuted,
631         classify(&cell, &evidence),
632     );
633     evidence = makeRow("queue-c-plus-one", 9, .reject);
634     evidence.outcomes.completed = 7;
635     evidence.outcomes.outstanding = 1;
636     try std.testing.expectEqual(
637         schema.Verdict.refuted,
638         classify(&cell, &evidence),
639     );
640     const at_capacity = capacityCell("queue-c", 8, .at_capacity);
641     evidence = makeRow("queue-c", 8, .none);
642     evidence.outcomes.completed = 7;
643     evidence.outcomes.outstanding = 1;
644     try std.testing.expectEqual(
645         schema.Verdict.refuted,
646         classify(&at_capacity, &evidence),
647     );
648 }
649 
650 test "qualification requires interval-backed interleaved SLO evidence" {
651     var cell = capacityCell("queue-slo", 8, .none);
652     cell.kind = .slo;
653     cell.evidence = .measurement;
654     cell.build_mode = .release_fast;
655     cell.repetitions = 4;
656     cell.threshold = .{
657         .kind = .absolute_max,
658         .value = 100,
659         .confidence_ppm = 950_000,
660     };
661     var evidence = makeRow("queue-slo", 8, .none);
662     evidence.evidence = .measurement;
663     evidence.statistics = .{
664         .present = true,
665         .baseline_profile_sha256 = digest(2),
666         .candidate_profile_sha256 = digest(2),
667         .sample_count = 4,
668         .baseline_count = 2,
669         .candidate_count = 2,
670         .minimum = 50,
671         .p50 = 60,
672         .p95 = 70,
673         .p99 = 80,
674         .maximum = 90,
675         .baseline_high_water = 8,
676         .candidate_high_water = 8,
677     };
678     try std.testing.expectEqual(
679         schema.Verdict.inconclusive,
680         classify(&cell, &evidence),
681     );
682     evidence.statistics.interval_present = true;
683     evidence.statistics.interleaved = true;
684     evidence.statistics.interval_low = 55;
685     evidence.statistics.interval_high = 95;
686     try std.testing.expectEqual(
687         schema.Verdict.pass,
688         classify(&cell, &evidence),
689     );
690     evidence.statistics.interval_high = 101;
691     try std.testing.expectEqual(
692         schema.Verdict.refuted,
693         classify(&cell, &evidence),
694     );
695 }
696 
697 test "qualification binds SLO evidence to the frozen execution profile" {
698     var cell = capacityCell("queue-slo", 8, .none);
699     cell.kind = .slo;
700     cell.evidence = .measurement;
701     cell.build_mode = .release_fast;
702     cell.repetitions = 4;
703     cell.threshold = .{
704         .kind = .baseline_delta_max,
705         .value = 100,
706         .confidence_ppm = 950_000,
707     };
708     const cells = [_]model.Cell{cell};
709     const plan = capacityPlan(&cells);
710     var row = makeRow("queue-slo", 8, .none);
711     row.evidence = .measurement;
712     row.verdict = .refuted;
713     row.statistics = .{
714         .present = true,
715         .interval_present = true,
716         .interleaved = true,
717         .baseline_profile_sha256 = digest(9),
718         .candidate_profile_sha256 = digest(9),
719         .sample_count = 4,
720         .baseline_count = 2,
721         .candidate_count = 2,
722         .minimum = 50,
723         .p50 = 60,
724         .p95 = 70,
725         .p99 = 80,
726         .maximum = 90,
727         .interval_low = 55,
728         .interval_high = 95,
729         .baseline_high_water = 8,
730         .candidate_high_water = 8,
731     };
732     const rows = [_]model.Row{row};
733     const summary = try evaluate(.{
734         .plan = plan,
735         .sha256 = digest(7),
736         .bytes = "{}",
737     }, &rows);
738     try std.testing.expectEqual(schema.Verdict.refuted, summary.verdict);
739 }
740 
741 test "qualification leaves excluded cells not exercised" {
742     var cell = capacityCell("queue-excluded", 8, .none);
743     cell.requirement = .excluded;
744     const cells = [_]model.Cell{
745         capacityCell("queue-required", 8, .none),
746         cell,
747     };
748     const plan = capacityPlan(&cells);
749     var excluded = makeRow("queue-excluded", 8, .none);
750     excluded.verdict = .not_exercised;
751     excluded.work = std.math.maxInt(u64);
752     const rows = [_]model.Row{
753         makeRow("queue-required", 8, .none),
754         excluded,
755     };
756     const summary = try evaluate(.{
757         .plan = plan,
758         .sha256 = digest(7),
759         .bytes = "{}",
760     }, &rows);
761     try std.testing.expectEqual(schema.Verdict.pass, summary.verdict);
762     try std.testing.expectEqual(@as(u16, 1), summary.not_exercised);
763     try std.testing.expectEqual(@as(u64, 8), summary.work);
764 }
765 
766 test "qualification rejects vacuous plans and assumptions cannot pass" {
767     var excluded = capacityCell("queue-excluded", 8, .none);
768     excluded.requirement = .excluded;
769     const excluded_cells = [_]model.Cell{excluded};
770     try std.testing.expectError(
771         error.PlanRequiredMissing,
772         validatePlan(capacityPlan(&excluded_cells)),
773     );
774     var assumption = capacityCell("queue-assumption", 8, .none);
775     assumption.evidence = .assumption;
776     var row = makeRow("queue-assumption", 8, .none);
777     row.evidence = .assumption;
778     try std.testing.expectEqual(
779         schema.Verdict.inconclusive,
780         classify(&assumption, &row),
781     );
782 }
783 
784 test "qualification rejects malformed threshold confidence" {
785     var cell = capacityCell("queue-slo", 8, .none);
786     cell.kind = .slo;
787     cell.evidence = .measurement;
788     cell.build_mode = .release_fast;
789     cell.repetitions = 2;
790     cell.threshold = .{
791         .kind = .absolute_max,
792         .value = 100,
793         .confidence_ppm = 1_000_001,
794     };
795     const cells = [_]model.Cell{cell};
796     try std.testing.expectError(
797         error.CellInvalid,
798         validatePlan(capacityPlan(&cells)),
799     );
800 }
801 
802 test "qualification rejects an unpaired bounded owner" {
803     const cells = [_]model.Cell{
804         capacityCell("queue-c", 8, .at_capacity),
805     };
806     try std.testing.expectError(
807         error.CapacityPairInvalid,
808         validatePlan(capacityPlan(&cells)),
809     );
810 }
811 
812 test "qualification records missing thresholds as inconclusive" {
813     var cells = [_]model.Cell{
814         capacityCell("queue-c", 8, .at_capacity),
815         capacityCell("queue-c-plus-one", 9, .over_capacity),
816     };
817     cells[0].threshold = .{ .kind = .none };
818     cells[1].threshold = .{ .kind = .none };
819     const plan = capacityPlan(&cells);
820     try validatePlan(plan);
821     var rows = [_]model.Row{
822         makeRow("queue-c", 8, .none),
823         makeRow("queue-c-plus-one", 9, .reject),
824     };
825     rows[0].capture = .missing_threshold;
826     rows[0].verdict = .inconclusive;
827     rows[1].capture = .missing_threshold;
828     rows[1].verdict = .inconclusive;
829     const summary = try evaluate(.{
830         .plan = plan,
831         .sha256 = digest(7),
832         .bytes = "{}",
833     }, &rows);
834     try std.testing.expectEqual(
835         schema.Verdict.inconclusive,
836         summary.verdict,
837     );
838     try std.testing.expectEqual(@as(u16, 2), summary.inconclusive);
839 }
840 
841 test "qualification requires equivalent C and C plus one state" {
842     const cells = [_]model.Cell{
843         capacityCell("queue-c", 8, .at_capacity),
844         capacityCell("queue-c-plus-one", 9, .over_capacity),
845     };
846     const plan = capacityPlan(&cells);
847     var rows = [_]model.Row{
848         makeRow("queue-c", 8, .none),
849         makeRow("queue-c-plus-one", 9, .reject),
850     };
851     rows[1].before_digest = digest(9);
852     rows[0].verdict = .refuted;
853     rows[1].verdict = .refuted;
854     const summary = try evaluate(.{
855         .plan = plan,
856         .sha256 = digest(7),
857         .bytes = "{}",
858     }, &rows);
859     try std.testing.expectEqual(schema.Verdict.refuted, summary.verdict);
860     try std.testing.expectEqual(@as(u16, 2), summary.refuted);
861     rows[1] = makeRow("queue-c-plus-one", 9, .reject);
862     rows[1].overload_count_before += 1;
863     rows[1].overload_count_after += 1;
864     rows[0].verdict = .refuted;
865     rows[1].verdict = .refuted;
866     const counter_summary = try evaluate(.{
867         .plan = plan,
868         .sha256 = digest(7),
869         .bytes = "{}",
870     }, &rows);
871     try std.testing.expectEqual(
872         schema.Verdict.refuted,
873         counter_summary.verdict,
874     );
875 }
876 
877 test "qualification requires declared overload and loss counters" {
878     const cell = capacityCell("queue-c-plus-one", 9, .over_capacity);
879     var rejected = makeRow("queue-c-plus-one", 9, .reject);
880     rejected.overload_count_after = rejected.overload_count_before;
881     try std.testing.expectEqual(
882         schema.Verdict.refuted,
883         classify(&cell, &rejected),
884     );
885     var dropped = makeRow("queue-c-plus-one", 9, .drop);
886     dropped.outcomes.accepted = 9;
887     dropped.outcomes.completed = 8;
888     dropped.outcomes.rejected = 0;
889     dropped.outcomes.dropped = 1;
890     dropped.loss_count_after = dropped.loss_count_before + 1;
891     try std.testing.expectEqual(
892         schema.Verdict.pass,
893         classify(&cell, &dropped),
894     );
895 }