From bab0bb2df2765111dda3f5e584fe5db338eea842 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Tue, 25 Aug 2026 20:36:26 +0300 Subject: [PATCH] feat(laboratory): publish M48S load envelope --- .../core/laboratory/m48sFixedClassDetector.ts | 155 +++++++++++ .../M48SFixedClassDetectorResult.tsx | 72 +++-- .../test/m48sFixedClassDetector.test.mjs | 55 ++++ config/laboratory-value-review.json | 2 +- .../m48s_fixed_class_detector_lab.py | 252 +++++++++++++++++- .../web/m48s_fixed_class_detector_lab_api.py | 75 ++++++ tests/test_m48s_fixed_class_detector_lab.py | 27 +- .../test_m48s_fixed_class_detector_lab_api.py | 9 + 8 files changed, 624 insertions(+), 23 deletions(-) diff --git a/apps/control-station/src/core/laboratory/m48sFixedClassDetector.ts b/apps/control-station/src/core/laboratory/m48sFixedClassDetector.ts index f1974ee..258eef7 100644 --- a/apps/control-station/src/core/laboratory/m48sFixedClassDetector.ts +++ b/apps/control-station/src/core/laboratory/m48sFixedClassDetector.ts @@ -2,6 +2,7 @@ import type { LaboratoryFetch } from "./advancedResults"; const RESULT_ID = /^m48s-fixed-class-detector-lab-[a-f0-9]{64}$/; const FRAME_ID = /^[0-9]{6}$/; +const SHA256 = /^[a-f0-9]{64}$/; const MODES = ["source", "yolox", "dfine", "rf-detr"] as const; const DETECTOR_MODES = ["yolox", "dfine", "rf-detr"] as const; const RESULT_STATUSES = [ @@ -105,6 +106,48 @@ export interface M48SRuntimeHardeningComparison { }; } +export interface M48SLoadEnvelopeScenario { + id: "production-10fps" | "reserve-12fps" | "limit-15fps"; + loadPurpose: "production-rate" | "reserve-gate" | "limit-discovery"; + requestedSourceRateHz: number; + sourceFramesAdmitted: number; + deliveredWorldStates: number; + supersededFrames: number; + deliveryRatio: number; + effectiveWorldStateFps: number; + worldStateCompletionAgeP95Ms: number; + worldStateCompletionAgeP99Ms: number; + worldStateCompletionAgeMaximumMs: number; + decodeP95Ms: number; + decodeMaximumMs: number; + detectorP95Ms: number; + detectorMaximumMs: number; + gpuUtilizationMeanPercent: number; + gpuUtilizationMaximumPercent: number; + gpuMemoryMaximumMib: number; + processPeakRssMib: number; + queueHighWatermarks: Readonly>; + queueCapacity: number; + additionalInferencePasses: number; + integrityGatePassed: boolean; + operatingTargetGatePassed: boolean; + thresholds: { + minimumDeliveryRatio: number; + minimumEffectiveWorldStateFps: number; + maximumWorldStateCompletionP95Ms: number; + }; +} + +export interface M48SLoadEnvelopeComparison { + productionRateRepeatabilityPassed: false; + reserve12FpsPassed: true; + limit15FpsPassed: true; + loadEnvelopeAccepted: false; + computeCapacityAtLeastFps: 15; + bottleneckInterpretation: "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation"; + scenarios: readonly M48SLoadEnvelopeScenario[]; +} + export interface M48SFixedClassDetectorResult { resultId: string; createdAtUtc: string; @@ -144,6 +187,7 @@ export interface M48SFixedClassDetectorResult { }; integratedWorldState: M48SIntegratedWorldState | null; runtimeHardening: M48SRuntimeHardeningComparison | null; + loadEnvelope: M48SLoadEnvelopeComparison | null; }; decision: { selectedCandidate: "rf-detr"; @@ -151,6 +195,11 @@ export interface M48SFixedClassDetectorResult { integratedWorldStateGateEvaluated: boolean; integratedWorldStateGatePassed: boolean; detectorReplacementAuthorized: false; + loadEnvelopeEvaluated: boolean; + productionRateRepeatabilityPassed: boolean; + reserve12FpsPassed: boolean; + limit15FpsPassed: boolean; + loadEnvelopeAccepted: boolean; productionAccepted: false; }; limitations: readonly string[]; @@ -457,6 +506,96 @@ function runtimeHardeningComparisonValue(value: unknown): M48SRuntimeHardeningCo }; } +function loadEnvelopeComparisonValue(value: unknown): M48SLoadEnvelopeComparison { + const comparison = objectValue(value, "M4.8S.metrics.load_envelope"); + exact( + comparison.schema_version, + "missioncore.m48s-load-envelope-comparison/v1", + "M4.8S.metrics.load_envelope.schema_version", + ); + exact(comparison.production_rate_repeatability_passed, false, "M4.8S.load_envelope.production_rate_repeatability_passed"); + exact(comparison.reserve_12_fps_passed, true, "M4.8S.load_envelope.reserve_12_fps_passed"); + exact(comparison.limit_15_fps_passed, true, "M4.8S.load_envelope.limit_15_fps_passed"); + exact(comparison.load_envelope_accepted, false, "M4.8S.load_envelope.load_envelope_accepted"); + exact(comparison.compute_capacity_at_least_fps, 15, "M4.8S.load_envelope.compute_capacity_at_least_fps"); + exact( + comparison.bottleneck_interpretation, + "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation", + "M4.8S.load_envelope.bottleneck_interpretation", + ); + const ids = ["production-10fps", "reserve-12fps", "limit-15fps"] as const; + const purposes = ["production-rate", "reserve-gate", "limit-discovery"] as const; + const targetGates = [false, true, true] as const; + const scenarios = arrayValue(comparison.scenarios, "M4.8S.load_envelope.scenarios").map((value, index) => { + const label = `M4.8S.load_envelope.scenarios[${index}]`; + const scenario = objectValue(value, label); + const id = ids[index]; + const purpose = purposes[index]; + if (id === undefined || purpose === undefined) { + throw new M48SFixedClassDetectorContractError("M4.8S.load_envelope.scenarios: лишний сценарий."); + } + exact(scenario.id, id, `${label}.id`); + exact(scenario.load_purpose, purpose, `${label}.load_purpose`); + exact(scenario.integrity_gate_passed, true, `${label}.integrity_gate_passed`); + exact(scenario.operating_target_gate_passed, targetGates[index], `${label}.operating_target_gate_passed`); + exact(scenario.additional_inference_passes, 0, `${label}.additional_inference_passes`); + const queues = integerRecordValue(scenario.queue_high_watermarks, `${label}.queue_high_watermarks`); + for (const key of ["detector", "geometry", "temporal", "rolling", "threat"] as const) { + if (!(key in queues)) { + throw new M48SFixedClassDetectorContractError(`${label}.queue_high_watermarks.${key}: отсутствует.`); + } + } + const thresholds = objectValue(scenario.thresholds, `${label}.thresholds`); + const frameEvidenceSha256 = textValue(scenario.frame_evidence_sha256, `${label}.frame_evidence_sha256`); + if (!SHA256.test(frameEvidenceSha256)) { + throw new M48SFixedClassDetectorContractError(`${label}.frame_evidence_sha256: нарушен SHA-256.`); + } + return { + id, + loadPurpose: purpose, + requestedSourceRateHz: numberValue(scenario.requested_source_rate_hz, `${label}.requested_source_rate_hz`), + sourceFramesAdmitted: integerValue(scenario.source_frames_admitted, `${label}.source_frames_admitted`), + deliveredWorldStates: integerValue(scenario.delivered_world_states, `${label}.delivered_world_states`), + supersededFrames: integerValue(scenario.superseded_frames, `${label}.superseded_frames`), + deliveryRatio: numberValue(scenario.delivery_ratio, `${label}.delivery_ratio`), + effectiveWorldStateFps: numberValue(scenario.effective_world_state_fps, `${label}.effective_world_state_fps`), + worldStateCompletionAgeP95Ms: numberValue(scenario.world_state_completion_age_p95_ms, `${label}.world_state_completion_age_p95_ms`), + worldStateCompletionAgeP99Ms: numberValue(scenario.world_state_completion_age_p99_ms, `${label}.world_state_completion_age_p99_ms`), + worldStateCompletionAgeMaximumMs: numberValue(scenario.world_state_completion_age_maximum_ms, `${label}.world_state_completion_age_maximum_ms`), + decodeP95Ms: numberValue(scenario.decode_p95_ms, `${label}.decode_p95_ms`), + decodeMaximumMs: numberValue(scenario.decode_maximum_ms, `${label}.decode_maximum_ms`), + detectorP95Ms: numberValue(scenario.detector_p95_ms, `${label}.detector_p95_ms`), + detectorMaximumMs: numberValue(scenario.detector_maximum_ms, `${label}.detector_maximum_ms`), + gpuUtilizationMeanPercent: numberValue(scenario.gpu_utilization_mean_percent, `${label}.gpu_utilization_mean_percent`), + gpuUtilizationMaximumPercent: numberValue(scenario.gpu_utilization_maximum_percent, `${label}.gpu_utilization_maximum_percent`), + gpuMemoryMaximumMib: numberValue(scenario.gpu_memory_maximum_mib, `${label}.gpu_memory_maximum_mib`), + processPeakRssMib: numberValue(scenario.process_peak_rss_mib, `${label}.process_peak_rss_mib`), + queueHighWatermarks: queues as M48SLoadEnvelopeScenario["queueHighWatermarks"], + queueCapacity: integerValue(scenario.queue_capacity, `${label}.queue_capacity`), + additionalInferencePasses: 0, + integrityGatePassed: true, + operatingTargetGatePassed: targetGates[index], + thresholds: { + minimumDeliveryRatio: numberValue(thresholds.minimum_delivery_ratio, `${label}.thresholds.minimum_delivery_ratio`), + minimumEffectiveWorldStateFps: numberValue(thresholds.minimum_effective_world_state_fps, `${label}.thresholds.minimum_effective_world_state_fps`), + maximumWorldStateCompletionP95Ms: numberValue(thresholds.maximum_world_state_completion_p95_ms, `${label}.thresholds.maximum_world_state_completion_p95_ms`), + }, + }; + }); + if (scenarios.length !== ids.length) { + throw new M48SFixedClassDetectorContractError("M4.8S.load_envelope.scenarios: неполный набор."); + } + return { + productionRateRepeatabilityPassed: false, + reserve12FpsPassed: true, + limit15FpsPassed: true, + loadEnvelopeAccepted: false, + computeCapacityAtLeastFps: 15, + bottleneckInterpretation: "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation", + scenarios, + }; +} + function parseResult(value: unknown, resultId: string): M48SFixedClassDetectorResult { const payload = objectValue(value, "M4.8S"); exact( @@ -475,6 +614,9 @@ function parseResult(value: unknown, resultId: string): M48SFixedClassDetectorRe const metrics = objectValue(payload.metrics, "M4.8S.metrics"); const load = objectValue(metrics.detector_load, "M4.8S.metrics.detector_load"); const decision = objectValue(payload.decision, "M4.8S.decision"); + const loadEnvelope = metrics.load_envelope === undefined + ? null + : loadEnvelopeComparisonValue(metrics.load_envelope); const cameraRaster = arrayValue(source.camera_raster, "M4.8S.source.camera_raster"); if (cameraRaster.length !== 2) { throw new M48SFixedClassDetectorContractError("M4.8S.source.camera_raster: нарушен размер."); @@ -503,6 +645,13 @@ function parseResult(value: unknown, resultId: string): M48SFixedClassDetectorRe exact(decision.detector_replacement_authorized, false, "M4.8S.decision.detector_replacement_authorized"); } exact(decision.production_accepted, false, "M4.8S.decision.production_accepted"); + if (loadEnvelope !== null) { + exact(decision.load_envelope_evaluated, true, "M4.8S.decision.load_envelope_evaluated"); + exact(decision.production_rate_repeatability_passed, false, "M4.8S.decision.production_rate_repeatability_passed"); + exact(decision.reserve_12_fps_passed, true, "M4.8S.decision.reserve_12_fps_passed"); + exact(decision.limit_15_fps_passed, true, "M4.8S.decision.limit_15_fps_passed"); + exact(decision.load_envelope_accepted, false, "M4.8S.decision.load_envelope_accepted"); + } const frames = arrayValue(payload.frames, "M4.8S.frames").map(descriptorValue); const evidenceFrameCount = integerValue(source.evidence_frame_count, "M4.8S.source.evidence_frame_count"); if (frames.length !== evidenceFrameCount || new Set(frames.map((frame) => frame.frameId)).size !== frames.length) { @@ -551,6 +700,7 @@ function parseResult(value: unknown, resultId: string): M48SFixedClassDetectorRe runtimeHardening: metrics.runtime_hardening === undefined ? null : runtimeHardeningComparisonValue(metrics.runtime_hardening), + loadEnvelope, }, decision: { selectedCandidate: "rf-detr", @@ -558,6 +708,11 @@ function parseResult(value: unknown, resultId: string): M48SFixedClassDetectorRe integratedWorldStateGateEvaluated: integratedGate, integratedWorldStateGatePassed: integratedGate, detectorReplacementAuthorized: false, + loadEnvelopeEvaluated: loadEnvelope !== null, + productionRateRepeatabilityPassed: false, + reserve12FpsPassed: loadEnvelope?.reserve12FpsPassed ?? false, + limit15FpsPassed: loadEnvelope?.limit15FpsPassed ?? false, + loadEnvelopeAccepted: false, productionAccepted: false, }, limitations: arrayValue(payload.limitations, "M4.8S.limitations").map((item, index) => textValue(item, `M4.8S.limitations[${index}]`)), diff --git a/apps/control-station/src/workspaces/laboratory/M48SFixedClassDetectorResult.tsx b/apps/control-station/src/workspaces/laboratory/M48SFixedClassDetectorResult.tsx index c7b93d9..7f29694 100644 --- a/apps/control-station/src/workspaces/laboratory/M48SFixedClassDetectorResult.tsx +++ b/apps/control-station/src/workspaces/laboratory/M48SFixedClassDetectorResult.tsx @@ -19,10 +19,19 @@ export function M48SFixedClassDetectorResultView({ result: M48SFixedClassDetectorResult; }) { const selected = result.metrics.candidates.find((candidate) => candidate.selected); - const load = result.metrics.detectorLoad; + const detectorLoad = result.metrics.detectorLoad; const integrated = result.metrics.integratedWorldState; const hardening = result.metrics.runtimeHardening; - const status = hardening + const loadEnvelope = result.metrics.loadEnvelope; + const production = loadEnvelope?.scenarios.find((scenario) => scenario.id === "production-10fps"); + const reserve = loadEnvelope?.scenarios.find((scenario) => scenario.id === "reserve-12fps"); + const limit = loadEnvelope?.scenarios.find((scenario) => scenario.id === "limit-15fps"); + const load = loadEnvelope && production && reserve && limit + ? { comparison: loadEnvelope, production, reserve, limit } + : null; + const status = load + ? "Capacity ≥ 15 FPS подтверждён; repeatability 10 FPS требует закрытия" + : hardening ? `После hardening: ${hardening.hardened.deliveredWorldStates.toLocaleString("ru-RU")} из ${hardening.hardened.sourceFramesAdmitted.toLocaleString("ru-RU")} world states доставлены` : integrated ? "Полный RF-DETR reference graph выдержал realtime shadow" @@ -32,9 +41,9 @@ export function M48SFixedClassDetectorResultView({ summary={( scenario.gpuMemoryMaximumMib)) / 1024, 2)} / ${decimal(Math.max(...load.comparison.scenarios.map((scenario) => scenario.processPeakRssMib)) / 1024, 2)} GiB`, hint: "общий GPU counter / graph process RSS" }, + ] : hardening ? [ { label: "World-state FPS", value: `${decimal(hardening.baseline.effectiveWorldStateFps, 3)} → ${decimal(hardening.hardened.effectiveWorldStateFps, 3)}`, hint: "до → после · target ≥ 9.5 FPS" }, { label: "Задержка p95", value: `${decimal(hardening.baseline.worldStateCompletionAgeP95Ms, 3)} → ${decimal(hardening.hardened.worldStateCompletionAgeP95Ms, 3)} ms`, hint: `p99 ${decimal(hardening.baseline.worldStateCompletionAgeP99Ms, 3)} → ${decimal(hardening.hardened.worldStateCompletionAgeP99Ms, 3)} ms` }, { label: "Максимальная задержка", value: `${decimal(hardening.baseline.worldStateCompletionAgeMaximumMs, 3)} → ${decimal(hardening.hardened.worldStateCompletionAgeMaximumMs, 3)} ms`, hint: "полный прогон без prewarm" }, @@ -101,18 +129,24 @@ export function M48SFixedClassDetectorResultView({ { label: "GPU / VRAM peak", value: `${decimal(integrated.gpuUtilizationMaximumPercent, 0)}% / ${decimal(integrated.gpuMemoryMaximumMib / 1024)} GiB`, hint: `GPU mean ${decimal(integrated.gpuUtilizationMeanPercent)}% · ${decimal(integrated.gpuPowerMaximumW)} W` }, ] : [ { label: "Detector capacity", value: `${decimal(selected?.capacityFps ?? 0)} FPS`, hint: "RF-DETR-L TensorRT/Triton" }, - { label: "Completion age p95", value: `${decimal(load.completionAgeP95Ms)} ms`, hint: "detector-only · target ≤ 175 ms" }, - { label: "Consumed / replaced", value: `${load.sourceFramesConsumed.toLocaleString("ru-RU")} / ${load.sourceFrameReplacements}`, hint: `${decimal(load.effectiveConsumedFps, 3)} source FPS · ${load.failures} failures` }, - { label: "GPU / VRAM peak", value: `${decimal(load.gpuUtilizationMaximumPercent, 0)}% / ${decimal(load.gpuMemoryMaximumMib / 1024)} GiB`, hint: `GPU mean ${decimal(load.gpuUtilizationMeanPercent)}% · queue ${load.queueMaximumDepth}/${load.queueCapacity}` }, + { label: "Completion age p95", value: `${decimal(detectorLoad.completionAgeP95Ms)} ms`, hint: "detector-only · target ≤ 175 ms" }, + { label: "Consumed / replaced", value: `${detectorLoad.sourceFramesConsumed.toLocaleString("ru-RU")} / ${detectorLoad.sourceFrameReplacements}`, hint: `${decimal(detectorLoad.effectiveConsumedFps, 3)} source FPS · ${detectorLoad.failures} failures` }, + { label: "GPU / VRAM peak", value: `${decimal(detectorLoad.gpuUtilizationMaximumPercent, 0)}% / ${decimal(detectorLoad.gpuMemoryMaximumMib / 1024)} GiB`, hint: `GPU mean ${decimal(detectorLoad.gpuUtilizationMeanPercent)}% · queue ${detectorLoad.queueMaximumDepth}/${detectorLoad.queueCapacity}` }, ]} conclusion={{ - proved: hardening + proved: load + ? `Полный single-pass graph сохранил bounded queues 2/2, нулевые failed/stale/rejected/unavailable и нулевые дополнительные inference-проходы во всех трёх режимах. Reserve 12 FPS прошёл с delivery ${(load.reserve.deliveryRatio * 100).toLocaleString("ru-RU", { maximumFractionDigits: 3 })}% и p95 ${decimal(load.reserve.worldStateCompletionAgeP95Ms, 3)} ms; 15 FPS также прошли с delivery ${(load.limit.deliveryRatio * 100).toLocaleString("ru-RU", { maximumFractionDigits: 3 })}%. Вычислительная capacity доказана как минимум до 15 FPS без более мощного Worker.` + : hardening ? `На полном сравнительном прогоне с ${hardening.hardened.sourceFramesAdmitted.toLocaleString("ru-RU")} одинаковыми входными кадрами hardening сохранил realtime FPS, доставил на ${(hardening.hardened.deliveredWorldStates - hardening.baseline.deliveredWorldStates).toLocaleString("ru-RU")} world states больше и сократил вытеснения ${hardening.baseline.supersededFrames} → ${hardening.hardened.supersededFrames}. Максимальный стоп rolling stage уменьшился ${decimal(hardening.baseline.rollingMaximumMs, 3)} → ${decimal(hardening.hardened.rollingMaximumMs, 3)} ms. Отдельный prewarm-прогон снизил первый detector frame ${decimal(hardening.startup.baseline.detectorMs, 3)} → ${decimal(hardening.startup.prewarmed.detectorMs, 3)} ms и первый world state ${decimal(hardening.startup.baseline.worldStateMs, 3)} → ${decimal(hardening.startup.prewarmed.worldStateMs, 3)} ms без дополнительного inference на кадрах.` : integrated ? `На Worker 006 полный граф обработал ${integrated.sourceFramesAdmitted.toLocaleString("ru-RU")} входных кадров, доставил ${integrated.deliveredWorldStates.toLocaleString("ru-RU")} состояний без ошибок, удержал все очереди в пределах ${integrated.queueCapacity} и p95 ${decimal(integrated.worldStateCompletionAgeP95Ms, 3)} ms. Advisory сформировал публикации по семействам: geometry-only (${integrated.advisoryFamilyCounts["generic-obstacle"].toLocaleString("ru-RU")}), люди (${integrated.advisoryFamilyCounts.person.toLocaleString("ru-RU")}), животные (${integrated.advisoryFamilyCounts.animal.toLocaleString("ru-RU")}) и транспорт (${integrated.advisoryFamilyCounts.vehicle.toLocaleString("ru-RU")}); это не количество уникальных физических объектов и не потребовало второго inference.` - : `RF-DETR-L ${decimal(load.durationSeconds / 60, 0)} минут устойчиво потреблял source-paced поток около 10 FPS: ${load.sourceFramesConsumed.toLocaleString("ru-RU")} кадров, 0 замен, 0 ошибок, completion-age p95 ${decimal(load.completionAgeP95Ms, 3)} ms.`, - notProved: "Не доказаны unbiased precision/recall классов, независимое качество track identity и risk policy, поведение planner или collision safety. Кадровые рамки не заменяют геометрическую occupancy-карту.", - decision: hardening + : `RF-DETR-L ${decimal(detectorLoad.durationSeconds / 60, 0)} минут устойчиво потреблял source-paced поток около 10 FPS: ${detectorLoad.sourceFramesConsumed.toLocaleString("ru-RU")} кадров, 0 замен, 0 ошибок, completion-age p95 ${decimal(detectorLoad.completionAgeP95Ms, 3)} ms.`, + notProved: load + ? `Не закрыта repeatability delivery на production-rate: 10 FPS дали ${(load.production.deliveryRatio * 100).toLocaleString("ru-RU", { maximumFractionDigits: 3 })}% при gate ${(load.production.thresholds.minimumDeliveryRatio * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%. Немонотонность 10 → 12 → 15 и decode maxima 3,2–3,6 s не позволяют объявить production envelope принятым.` + : "Не доказаны unbiased precision/recall классов, независимое качество track identity и risk policy, поведение planner или collision safety. Кадровые рамки не заменяют геометрическую occupancy-карту.", + decision: load + ? "Не усиливать GPU и не добавлять второй детектор. Сначала изолировать/предзагружать video decode и измерить scheduler tail, затем повторить тот же sealed 10/12/15 FPS набор серией прогонов. До закрытия repeatability production switch не разрешён." + : hardening ? "Сохранить разгрузку cyclic GC и обязательный detector prewarm как текущий runtime baseline. Следующий этап — измерять запас realtime при росте сцены и числа семантических объектов, не добавляя второй детектор и не расширяя inference-нагрузку. Production switch не разрешён." : "Сохранить RF-DETR-L как risk-semantic shadow provider полного reference graph. Не классифицировать миллионы статических форм: неизвестное неподвижное препятствие остаётся geometry-owned и объезжается; классы сохраняются для людей, животных и транспорта. Production switch не разрешён.", }} diff --git a/apps/control-station/test/m48sFixedClassDetector.test.mjs b/apps/control-station/test/m48sFixedClassDetector.test.mjs index 8730793..d110034 100644 --- a/apps/control-station/test/m48sFixedClassDetector.test.mjs +++ b/apps/control-station/test/m48sFixedClassDetector.test.mjs @@ -45,6 +45,38 @@ function resultPayload() { selected: false, ...overrides, }); + const loadScenario = (id, loadPurpose, rate, delivered, superseded, targetPassed) => ({ + id, + load_purpose: loadPurpose, + requested_source_rate_hz: rate, + source_frames_admitted: 4489, + delivered_world_states: delivered, + superseded_frames: superseded, + delivery_ratio: delivered / 4489, + effective_world_state_fps: rate * 0.975, + world_state_completion_age_p95_ms: 75, + world_state_completion_age_p99_ms: 100, + world_state_completion_age_maximum_ms: 400, + decode_p95_ms: 12, + decode_maximum_ms: 3200, + detector_p95_ms: 40, + detector_maximum_ms: 370, + gpu_utilization_mean_percent: 35, + gpu_utilization_maximum_percent: 85, + gpu_memory_maximum_mib: 9584, + process_peak_rss_mib: 2327, + queue_high_watermarks: { detector: 2, geometry: 2, temporal: 2, rolling: 2, threat: 2 }, + queue_capacity: 2, + additional_inference_passes: 0, + integrity_gate_passed: true, + operating_target_gate_passed: targetPassed, + thresholds: { + minimum_delivery_ratio: rate === 10 ? 0.999 : rate === 12 ? 0.995 : 0.95, + minimum_effective_world_state_fps: rate * 0.95, + maximum_world_state_completion_p95_ms: rate === 10 ? 125 : rate === 12 ? 150 : 175, + }, + frame_evidence_sha256: "8".repeat(64), + }); return { schema_version: "missioncore.m48s-fixed-class-detector-result-view/v1", result_id: resultId, @@ -172,6 +204,20 @@ function resultPayload() { validation_frames: 1000, }, }, + load_envelope: { + schema_version: "missioncore.m48s-load-envelope-comparison/v1", + production_rate_repeatability_passed: false, + reserve_12_fps_passed: true, + limit_15_fps_passed: true, + load_envelope_accepted: false, + compute_capacity_at_least_fps: 15, + bottleneck_interpretation: "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation", + scenarios: [ + loadScenario("production-10fps", "production-rate", 10, 4483, 6, false), + loadScenario("reserve-12fps", "reserve-gate", 12, 4477, 12, true), + loadScenario("limit-15fps", "limit-discovery", 15, 4488, 1, true), + ], + }, }, decision: { selected_candidate: "rf-detr", @@ -179,6 +225,11 @@ function resultPayload() { integrated_world_state_gate_evaluated: true, integrated_world_state_gate_passed: true, detector_replacement_authorized: false, + load_envelope_evaluated: true, + production_rate_repeatability_passed: false, + reserve_12_fps_passed: true, + limit_15_fps_passed: true, + load_envelope_accepted: false, production_accepted: false, }, limitations: ["No independent semantic ground truth."], @@ -212,6 +263,10 @@ test("M4.8S result exposes complete graph load without production authority", as assert.equal(result.metrics.runtimeHardening.baseline.supersededFrames, 9); assert.equal(result.metrics.runtimeHardening.hardened.supersededFrames, 1); assert.equal(result.metrics.runtimeHardening.startup.prewarmed.worldStateMs, 60.516957); + assert.equal(result.metrics.loadEnvelope.scenarios[0].operatingTargetGatePassed, false); + assert.equal(result.metrics.loadEnvelope.scenarios[1].deliveredWorldStates, 4477); + assert.equal(result.metrics.loadEnvelope.computeCapacityAtLeastFps, 15); + assert.equal(result.decision.loadEnvelopeAccepted, false); assert.equal(result.decision.integratedWorldStateGatePassed, true); assert.equal(result.decision.productionAccepted, false); assert.equal(result.authority.navigationOrSafetyAccepted, false); diff --git a/config/laboratory-value-review.json b/config/laboratory-value-review.json index 4b8cdb7..f6d9ffa 100644 --- a/config/laboratory-value-review.json +++ b/config/laboratory-value-review.json @@ -249,7 +249,7 @@ }, { "catalog_id": "m48s-fixed-class-detector", - "evidence_id": "m48s-fixed-class-detector-lab-7411aadc35f5b61b97ee59c983e125e0a3781612d399c4dfa779b272eeb0e56e", + "evidence_id": "m48s-fixed-class-detector-lab-4a901d811f53734540337f8d1f2c01666539aa0a9b7786f26d4f1f2e08cc858a", "signal": "progress", "lifecycle": "current", "visual_evidence": "available" diff --git a/src/k1link/laboratory/m48s_fixed_class_detector_lab.py b/src/k1link/laboratory/m48s_fixed_class_detector_lab.py index d755845..0f98abb 100644 --- a/src/k1link/laboratory/m48s_fixed_class_detector_lab.py +++ b/src/k1link/laboratory/m48s_fixed_class_detector_lab.py @@ -22,6 +22,7 @@ CATALOG_SCHEMA: Final = "missioncore.m48s-fixed-class-detector-frame-catalog/v1" FRAME_SCHEMA: Final = "missioncore.m48s-fixed-class-detector-frame/v1" REPORT_SCHEMA: Final = "missioncore.m48s-fixed-class-detector-report/v1" RUNTIME_HARDENING_SCHEMA: Final = "missioncore.m48s-runtime-hardening-comparison/v1" +LOAD_ENVELOPE_SCHEMA: Final = "missioncore.m48s-load-envelope-comparison/v1" METHOD_SCHEMA: Final = "missioncore.laboratory-method/v1" RESULT_PREFIX: Final = "m48s-fixed-class-detector-lab-" TOURNAMENT_ID: Final = ( @@ -65,6 +66,47 @@ RUNTIME_HARDENING_RUNS: Final = { "frame_count": 1000, }, } +LOAD_ENVELOPE_RUNS: Final = { + "production-10fps": { + "run_id": "m48s-load-envelope-v1-production-10fps-a1", + "load_purpose": "production-rate", + "source_rate_hz": 10.0, + "result_sha256": "60d4d82a8d47e5087019640353c9f8f7ddae518b52801dfd8c64423f5aba9fe2", + "frames_sha256": "9e836f13a49390be498ce2b133fe2c20538af43adf667b0e7b89dc6349379ab0", + "delivered": 4483, + "superseded": 6, + "operating_target_gate_passed": False, + }, + "reserve-12fps": { + "run_id": "m48s-load-envelope-v1-reserve-12fps-a1", + "load_purpose": "reserve-gate", + "source_rate_hz": 12.0, + "result_sha256": "c8cb917f6f3a8cb6ffcc5146211066dbaed4aa2d8671e55820c266c49d5d5321", + "frames_sha256": "3c1e635f6fbea643024bbd1711fb6c1ed5380430972cbb434ec76f4488d1c8b7", + "delivered": 4477, + "superseded": 12, + "operating_target_gate_passed": True, + }, + "limit-15fps": { + "run_id": "m48s-load-envelope-v1-limit-15fps-a1", + "load_purpose": "limit-discovery", + "source_rate_hz": 15.0, + "result_sha256": "a78767ee2a7d8f3f7f7625c408e9bc8fb6c60e8ba6f77ab54fddbca018540713", + "frames_sha256": "8be16ed155111ffa7093bbf46ceda4bc679593bfaf7a00c38c5d50c2385138d0", + "delivered": 4488, + "superseded": 1, + "operating_target_gate_passed": True, + }, +} +LOAD_ENVELOPE_PROFILE_SHA256: Final = ( + "d63c711534d84ee526d056885cfa0b5221df61d8394b244188b735bb34d20869" +) +LOAD_RUNTIME_ARTIFACT_SHA256: Final = ( + "c2344c91ce48111abb364cad38bd73fca9e5b30c8d001c113718dc1b30a8d859" +) +LOAD_RUNNER_SHA256: Final = ( + "0ff2ed9bdf5d5982cce08c4930853f65aa7be2c5986afacfe6a7cf925a2815e9" +) YOLOX_ID: Final = ( "m48s-yolox-all-coco-shadow-7dbe6043b3fc12c7ddb162f609f883d86b34a4f2dd3785a632795f257e192d06" ) @@ -149,6 +191,11 @@ def build_m48s_fixed_class_detector_lab( } for name, definition in RUNTIME_HARDENING_RUNS.items() } + load_envelope_profile_path = repository / "config/perception/m48s-load-envelope-v1.json" + load_envelope_paths = { + name: runtime / "load-envelope-worker" / str(definition["run_id"]) / "result.json" + for name, definition in LOAD_ENVELOPE_RUNS.items() + } yolox_root = runtime / "yolox-all-coco-results" / YOLOX_ID yolox_manifest_path = yolox_root / "manifest.json" yolox_frames_path = yolox_root / "frames.jsonl" @@ -171,6 +218,8 @@ def build_m48s_fixed_class_detector_lab( dfine_path, rf_detr_path, profile_path, + load_envelope_profile_path, + *load_envelope_paths.values(), *(path for paths in hardening_paths.values() for path in paths.values()), ): if path.is_symlink() or not path.is_file(): @@ -188,6 +237,10 @@ def build_m48s_fixed_class_detector_lab( hardening_runs = { name: _read_object(paths["result"]) for name, paths in hardening_paths.items() } + load_envelope_profile = _read_object(load_envelope_profile_path) + load_envelope_runs = { + name: _read_object(path) for name, path in load_envelope_paths.items() + } hardening_first_frames = { name: _read_first_jsonl_object(paths["frames"]) for name, paths in hardening_paths.items() @@ -215,6 +268,10 @@ def build_m48s_fixed_class_detector_lab( hardening_runs=hardening_runs, hardening_first_frames=hardening_first_frames, hardening_paths=hardening_paths, + load_envelope_profile=load_envelope_profile, + load_envelope_profile_path=load_envelope_profile_path, + load_envelope_runs=load_envelope_runs, + load_envelope_paths=load_envelope_paths, ) source_paths = {frame_id: source_root / f"frame-{frame_id}.jpg" for frame_id in FRAME_IDS} @@ -277,6 +334,16 @@ def build_m48s_fixed_class_detector_lab( } for name, paths in hardening_paths.items() }, + "load_envelope": { + "profile_sha256": sha256_path(load_envelope_profile_path), + "runs": { + name: { + "result_sha256": sha256_path(load_envelope_paths[name]), + "frames_sha256": run["execution"]["frame_evidence"]["sha256"], + } + for name, run in load_envelope_runs.items() + }, + }, "yolox_result_id": YOLOX_ID, "yolox_document_sha256": sha256_path(yolox_manifest_path), }, @@ -285,7 +352,7 @@ def build_m48s_fixed_class_detector_lab( } identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest() result_id = RESULT_PREFIX + identity_sha256 - completed_utc_ns = hardening_runs["prewarmed"].get("completed_utc_ns") + completed_utc_ns = load_envelope_runs["limit-15fps"].get("completed_utc_ns") if not isinstance(completed_utc_ns, int) or isinstance(completed_utc_ns, bool): raise M48SFixedClassDetectorLabError( "complete reference-graph completion time is unavailable" @@ -309,6 +376,7 @@ def build_m48s_fixed_class_detector_lab( candidates=candidates, hardening_runs=hardening_runs, hardening_first_frames=hardening_first_frames, + load_envelope_runs=load_envelope_runs, ) decision = { "bounded_question_accepted": True, @@ -317,6 +385,11 @@ def build_m48s_fixed_class_detector_lab( "integrated_world_state_gate_evaluated": True, "integrated_world_state_gate_passed": True, "full_replay_visual_published": True, + "load_envelope_evaluated": True, + "production_rate_repeatability_passed": False, + "reserve_12_fps_passed": True, + "limit_15_fps_passed": True, + "load_envelope_accepted": False, "detector_replacement_authorized": False, "production_accepted": False, } @@ -335,6 +408,11 @@ def build_m48s_fixed_class_detector_lab( "The visual timeline is the original qualified semantic replay; runtime-hardening " "metrics come from separately sealed, input-identical replay runs." ), + ( + "The 12 and 15 FPS operating gates passed, but the separately predeclared 10 FPS " + "delivery-ratio gate missed by two frames; capacity is demonstrated while delivery " + "repeatability remains open." + ), ] root = output_root.expanduser().absolute() @@ -421,6 +499,12 @@ def build_m48s_fixed_class_detector_lab( ) for name, paths in hardening_paths.items(): shutil.copyfile(paths["result"], temporary / f"runtime-hardening-{name}.json") + shutil.copyfile( + load_envelope_profile_path, + temporary / "runtime-load-envelope-profile.json", + ) + for name, path in load_envelope_paths.items(): + shutil.copyfile(path, temporary / f"runtime-load-{name}.json") startup_evidence = { "schema_version": RUNTIME_HARDENING_SCHEMA, "source_frames_sha256": { @@ -441,11 +525,15 @@ def build_m48s_fixed_class_detector_lab( "detector_load": load["execution"], "complete_reference_graph": reference_graph["identity"]["evidence"]["execution"], "runtime_hardening": metrics["runtime_hardening"], + "load_envelope": metrics["load_envelope"], }, "metrics": metrics, "acceptance": { "detector_load": load["checks"], "complete_reference_graph": reference_graph["identity"]["evidence"]["checks"], + "load_envelope": { + name: run["checks"] for name, run in load_envelope_runs.items() + }, }, "decision": decision, "limitations": limitations, @@ -515,6 +603,10 @@ def _validate_inputs( hardening_runs: dict[str, dict[str, Any]], hardening_first_frames: dict[str, dict[str, Any]], hardening_paths: dict[str, dict[str, Path]], + load_envelope_profile: dict[str, Any], + load_envelope_profile_path: Path, + load_envelope_runs: dict[str, dict[str, Any]], + load_envelope_paths: dict[str, Path], ) -> None: decision = deployment.get("decision") graph_identity = reference_graph.get("identity") @@ -654,6 +746,101 @@ def _validate_inputs( raise M48SFixedClassDetectorLabError( f"runtime-hardening startup evidence changed: {name}" ) + _validate_load_envelope( + profile=load_envelope_profile, + profile_path=load_envelope_profile_path, + runs=load_envelope_runs, + paths=load_envelope_paths, + ) + + +def _validate_load_envelope( + *, + profile: dict[str, Any], + profile_path: Path, + runs: dict[str, dict[str, Any]], + paths: dict[str, Path], +) -> None: + scenarios = profile.get("scenarios") + if ( + profile.get("schema_version") != "missioncore.m48s-load-envelope-profile/v1" + or profile.get("profile_id") != "m48s-rf-detr-reference-graph-load-envelope/v1" + or sha256_path(profile_path) != LOAD_ENVELOPE_PROFILE_SHA256 + or not isinstance(scenarios, list) + or len(scenarios) != len(LOAD_ENVELOPE_RUNS) + ): + raise M48SFixedClassDetectorLabError("load-envelope profile identity changed") + scenarios_by_id = { + scenario.get("id"): scenario for scenario in scenarios if isinstance(scenario, dict) + } + if set(scenarios_by_id) != set(LOAD_ENVELOPE_RUNS): + raise M48SFixedClassDetectorLabError("load-envelope scenarios changed") + shared_inputs: dict[str, object] | None = None + for name, definition in LOAD_ENVELOPE_RUNS.items(): + run = runs.get(name) + path = paths.get(name) + scenario = scenarios_by_id[name] + if not isinstance(run, dict) or not isinstance(path, Path): + raise M48SFixedClassDetectorLabError("load-envelope evidence is incomplete") + source = run.get("source") + identity = run.get("identity") + execution = run.get("execution") + integrity_checks = run.get("integrity_checks") + thresholds = run.get("predeclared_thresholds") + frame_evidence = execution.get("frame_evidence") if isinstance(execution, dict) else None + terminal = execution.get("terminal_outcomes") if isinstance(execution, dict) else None + pipeline = run.get("metrics", {}).get("pipeline_timing") + if ( + run.get("schema_version") != "missioncore.m48s-reference-graph-shadow-load/v4" + or run.get("completed") is not True + or run.get("production_accepted") is not False + or run.get("authority") != false_authority() + or run.get("evidence_integrity_gate_passed") is not True + or run.get("operating_target_gate_passed") + is not definition["operating_target_gate_passed"] + or not isinstance(integrity_checks, dict) + or not integrity_checks + or not all(value is True for value in integrity_checks.values()) + or not isinstance(source, dict) + or source.get("source_id") != "RAVNOVES00" + or source.get("pacing_contract") + != "wall-clock-scaled-source-timestamps-immutable/v1" + or source.get("requested_rate_hz") != definition["source_rate_hz"] + or not isinstance(identity, dict) + or identity.get("worker_id") != "worker-006" + or identity.get("graph_id") != "reference-perception-graph/v2" + or identity.get("detector_provider_id") + != "triton-rf-detr-large-coco-risk-fp16-shadow/v0" + or identity.get("runtime_artifact_sha256") != LOAD_RUNTIME_ARTIFACT_SHA256 + or identity.get("runner_sha256") != LOAD_RUNNER_SHA256 + or not isinstance(execution, dict) + or execution.get("load_purpose") != definition["load_purpose"] + or execution.get("requested_source_rate_hz") != definition["source_rate_hz"] + or execution.get("source_timestamps_preserved") is not True + or execution.get("admitted_frames") != 4489 + or execution.get("delivered_world_states") != definition["delivered"] + or not isinstance(terminal, dict) + or terminal.get("delivered") != definition["delivered"] + or terminal.get("superseded") != definition["superseded"] + or sum(terminal.values()) != 4489 + or not isinstance(frame_evidence, dict) + or frame_evidence.get("row_count") != definition["delivered"] + or frame_evidence.get("sha256") != definition["frames_sha256"] + or not isinstance(pipeline, dict) + or pipeline.get("additional_inference_passes") != 0 + or thresholds != scenario.get("thresholds") + or sha256_path(path) != definition["result_sha256"] + ): + raise M48SFixedClassDetectorLabError( + f"sealed load-envelope evidence changed: {name}" + ) + inputs = identity.get("inputs") + if not isinstance(inputs, dict): + raise M48SFixedClassDetectorLabError("load-envelope input identity is unavailable") + if shared_inputs is None: + shared_inputs = inputs + elif inputs != shared_inputs: + raise M48SFixedClassDetectorLabError("load-envelope runs changed graph inputs") def _method( @@ -791,6 +978,7 @@ def _metrics( candidates: list[dict[str, object]], hardening_runs: dict[str, dict[str, Any]], hardening_first_frames: dict[str, dict[str, Any]], + load_envelope_runs: dict[str, dict[str, Any]], ) -> dict[str, object]: graph_evidence = reference_graph["identity"]["evidence"] graph_execution = graph_evidence["execution"] @@ -860,6 +1048,62 @@ def _metrics( runs=hardening_runs, first_frames=hardening_first_frames, ), + "load_envelope": _load_envelope_metrics(load_envelope_runs), + } + + +def _load_envelope_metrics(runs: dict[str, dict[str, Any]]) -> dict[str, object]: + scenarios: list[dict[str, object]] = [] + for name in LOAD_ENVELOPE_RUNS: + run = runs[name] + execution = run["execution"] + metrics = run["metrics"] + completion = metrics["world_state_completion_age_ms"] + gpu = metrics["gpu"] + pipeline = metrics["pipeline_timing"] + terminal = execution["terminal_outcomes"] + thresholds = run["predeclared_thresholds"] + scenarios.append( + { + "id": name, + "load_purpose": execution["load_purpose"], + "requested_source_rate_hz": execution["requested_source_rate_hz"], + "source_frames_admitted": execution["admitted_frames"], + "delivered_world_states": execution["delivered_world_states"], + "superseded_frames": terminal.get("superseded", 0), + "delivery_ratio": execution["delivery_ratio"], + "effective_world_state_fps": execution["effective_world_state_fps"], + "world_state_completion_age_p95_ms": completion["p95"], + "world_state_completion_age_p99_ms": completion["p99"], + "world_state_completion_age_maximum_ms": completion["maximum"], + "decode_p95_ms": pipeline["decode_duration_ms"]["p95"], + "decode_maximum_ms": pipeline["decode_duration_ms"]["maximum"], + "detector_p95_ms": pipeline["detector_ms"]["total"]["p95"], + "detector_maximum_ms": pipeline["detector_ms"]["total"]["maximum"], + "gpu_utilization_mean_percent": gpu["gpu_utilization_percent"]["mean"], + "gpu_utilization_maximum_percent": gpu["gpu_utilization_percent"]["maximum"], + "gpu_memory_maximum_mib": gpu["gpu_memory_used_mib"]["maximum"], + "process_peak_rss_mib": metrics["process_peak_rss_after_mib"], + "queue_high_watermarks": execution["queue_high_watermarks"], + "queue_capacity": 2, + "additional_inference_passes": pipeline["additional_inference_passes"], + "integrity_gate_passed": run["evidence_integrity_gate_passed"], + "operating_target_gate_passed": run["operating_target_gate_passed"], + "thresholds": thresholds, + "frame_evidence_sha256": execution["frame_evidence"]["sha256"], + } + ) + return { + "schema_version": LOAD_ENVELOPE_SCHEMA, + "production_rate_repeatability_passed": False, + "reserve_12_fps_passed": True, + "limit_15_fps_passed": True, + "load_envelope_accepted": False, + "compute_capacity_at_least_fps": 15.0, + "bottleneck_interpretation": ( + "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation" + ), + "scenarios": scenarios, } @@ -1016,6 +1260,12 @@ def _artifact_manifest(root: Path) -> list[dict[str, object]]: RUNTIME_HARDENING_SCHEMA if relative == "runtime-hardening-startup.json" else None ) role = "upstream-runtime-hardening-evidence" + elif relative == "runtime-load-envelope-profile.json": + schema_version = "missioncore.m48s-load-envelope-profile/v1" + role = "predeclared-load-envelope-profile" + elif relative.startswith("runtime-load-"): + schema_version = "missioncore.m48s-reference-graph-shadow-load/v4" + role = "upstream-load-envelope-evidence" artifacts.append( { "role": role, diff --git a/src/k1link/web/m48s_fixed_class_detector_lab_api.py b/src/k1link/web/m48s_fixed_class_detector_lab_api.py index 0bc452e..dfb2302 100644 --- a/src/k1link/web/m48s_fixed_class_detector_lab_api.py +++ b/src/k1link/web/m48s_fixed_class_detector_lab_api.py @@ -331,6 +331,16 @@ def _load_result_uncached(candidate: Path) -> dict[str, Any]: or decision.get("integrated_world_state_gate_evaluated") is not integrated or (integrated and decision.get("integrated_world_state_gate_passed") is not True) or (integrated and decision.get("detector_replacement_authorized") is not False) + or ( + metrics.get("load_envelope") is not None + and ( + decision.get("load_envelope_evaluated") is not True + or decision.get("production_rate_repeatability_passed") is not False + or decision.get("reserve_12_fps_passed") is not True + or decision.get("limit_15_fps_passed") is not True + or decision.get("load_envelope_accepted") is not False + ) + ) or decision.get("production_accepted") is not False or not isinstance(method, dict) or method.get("schema_version") != "missioncore.laboratory-method/v1" @@ -341,6 +351,10 @@ def _load_result_uncached(candidate: Path) -> dict[str, Any]: metrics.get("runtime_hardening") is not None and not _valid_runtime_hardening(metrics["runtime_hardening"]) ) + or ( + metrics.get("load_envelope") is not None + and not _valid_load_envelope(metrics["load_envelope"]) + ) or not isinstance(manifest.get("limitations"), list) or not isinstance(catalog_descriptor, dict) or catalog_descriptor.get("path") != "catalog.json" @@ -420,6 +434,67 @@ def _valid_runtime_hardening(value: object) -> bool: ) +def _valid_load_envelope(value: object) -> bool: + if not isinstance(value, dict) or value.get("schema_version") != ( + "missioncore.m48s-load-envelope-comparison/v1" + ): + return False + if ( + value.get("production_rate_repeatability_passed") is not False + or value.get("reserve_12_fps_passed") is not True + or value.get("limit_15_fps_passed") is not True + or value.get("load_envelope_accepted") is not False + or value.get("compute_capacity_at_least_fps") != 15.0 + or value.get("bottleneck_interpretation") + != "rare-source-decode-or-scheduling-tail-not-steady-gpu-saturation" + ): + return False + scenarios = value.get("scenarios") + scenario_ids = [item.get("id") for item in scenarios if isinstance(item, dict)] if ( + isinstance(scenarios, list) + ) else [] + if scenario_ids != ["production-10fps", "reserve-12fps", "limit-15fps"]: + return False + expected_targets = (False, True, True) + required_numeric = { + "requested_source_rate_hz", + "source_frames_admitted", + "delivered_world_states", + "superseded_frames", + "delivery_ratio", + "effective_world_state_fps", + "world_state_completion_age_p95_ms", + "world_state_completion_age_p99_ms", + "world_state_completion_age_maximum_ms", + "decode_p95_ms", + "decode_maximum_ms", + "detector_p95_ms", + "detector_maximum_ms", + "gpu_utilization_mean_percent", + "gpu_utilization_maximum_percent", + "gpu_memory_maximum_mib", + "process_peak_rss_mib", + "queue_capacity", + "additional_inference_passes", + } + for scenario, target in zip(scenarios, expected_targets, strict=True): + if ( + not isinstance(scenario, dict) + or scenario.get("integrity_gate_passed") is not True + or scenario.get("operating_target_gate_passed") is not target + or scenario.get("additional_inference_passes") != 0 + or scenario.get("queue_capacity") != 2 + or not isinstance(scenario.get("queue_high_watermarks"), dict) + or any(value != 2 for value in scenario["queue_high_watermarks"].values()) + or not isinstance(scenario.get("thresholds"), dict) + or any(not _nonnegative_number(scenario.get(key)) for key in required_numeric) + or not isinstance(scenario.get("frame_evidence_sha256"), str) + or SHA256.fullmatch(scenario["frame_evidence_sha256"]) is None + ): + return False + return True + + def _nonnegative_number(value: object) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) and float(value) >= 0.0 diff --git a/tests/test_m48s_fixed_class_detector_lab.py b/tests/test_m48s_fixed_class_detector_lab.py index 94f68f2..7907e86 100644 --- a/tests/test_m48s_fixed_class_detector_lab.py +++ b/tests/test_m48s_fixed_class_detector_lab.py @@ -38,7 +38,7 @@ def test_m48s_lab_seals_visual_comparison_and_load_evidence(tmp_path: Path) -> N assert manifest["result_id"] == result.result_id assert result.result_id.endswith(identity_digest) assert manifest["identity_sha256"] == identity_digest - assert len(manifest["artifacts"]) == 35 + assert len(manifest["artifacts"]) == 39 assert manifest["method"]["completeness"] == "complete" assert manifest["bounded_question_accepted"] is True assert manifest["ground_truth"] is False @@ -49,6 +49,11 @@ def test_m48s_lab_seals_visual_comparison_and_load_evidence(tmp_path: Path) -> N "integrated_world_state_gate_evaluated": True, "integrated_world_state_gate_passed": True, "full_replay_visual_published": True, + "load_envelope_evaluated": True, + "production_rate_repeatability_passed": False, + "reserve_12_fps_passed": True, + "limit_15_fps_passed": True, + "load_envelope_accepted": False, "detector_replacement_authorized": False, "production_accepted": False, } @@ -80,6 +85,24 @@ def test_m48s_lab_seals_visual_comparison_and_load_evidence(tmp_path: Path) -> N assert hardening["startup"]["prewarmed"]["world_state_ms"] == 60.516957 assert hardening["startup"]["prewarm_duration_ms"] == 420.721918 assert hardening["startup"]["validation_frames"] == 1_000 + envelope = manifest["metrics"]["load_envelope"] + assert envelope["schema_version"] == "missioncore.m48s-load-envelope-comparison/v1" + assert envelope["production_rate_repeatability_passed"] is False + assert envelope["reserve_12_fps_passed"] is True + assert envelope["limit_15_fps_passed"] is True + assert envelope["load_envelope_accepted"] is False + assert envelope["compute_capacity_at_least_fps"] == 15.0 + production, reserve, limit = envelope["scenarios"] + assert production["delivered_world_states"] == 4_483 + assert production["superseded_frames"] == 6 + assert production["operating_target_gate_passed"] is False + assert reserve["delivered_world_states"] == 4_477 + assert reserve["superseded_frames"] == 12 + assert reserve["operating_target_gate_passed"] is True + assert limit["delivered_world_states"] == 4_488 + assert limit["superseded_frames"] == 1 + assert limit["operating_target_gate_passed"] is True + assert all(item["additional_inference_passes"] == 0 for item in envelope["scenarios"]) catalog = json.loads((result.result_root / "catalog.json").read_text("utf-8")) assert catalog["frame_count"] == len(FRAME_IDS) == 11 @@ -99,7 +122,7 @@ def test_m48s_lab_seals_visual_comparison_and_load_evidence(tmp_path: Path) -> N ) proof = verify_laboratory_evidence_result(definition, result.result_root) assert proof["result_id"] == result.result_id - assert proof["artifact_count"] == 35 + assert proof["artifact_count"] == 39 with pytest.raises(M48SFixedClassDetectorLabError, match="already exists"): build_m48s_fixed_class_detector_lab( diff --git a/tests/test_m48s_fixed_class_detector_lab_api.py b/tests/test_m48s_fixed_class_detector_lab_api.py index 46059ba..dca0e95 100644 --- a/tests/test_m48s_fixed_class_detector_lab_api.py +++ b/tests/test_m48s_fixed_class_detector_lab_api.py @@ -51,6 +51,15 @@ def test_m48s_lab_api_projects_verified_result_frame_and_camera(tmp_path: Path) assert hardening["baseline"]["delivered_world_states"] == 4_480 assert hardening["hardened"]["delivered_world_states"] == 4_488 assert hardening["startup"]["prewarmed"]["world_state_ms"] == 60.516957 + envelope = result.json()["metrics"]["load_envelope"] + assert envelope["production_rate_repeatability_passed"] is False + assert envelope["reserve_12_fps_passed"] is True + assert envelope["limit_15_fps_passed"] is True + assert [item["requested_source_rate_hz"] for item in envelope["scenarios"]] == [ + 10.0, + 12.0, + 15.0, + ] assert result.json()["ground_truth"] is False assert len(result.json()["frames"]) == 11