5 Commits
44 changed files with 5134 additions and 74 deletions
@@ -41,8 +41,25 @@ export interface LaboratoryMetricCorridorVisual {
halfWidthM: number;
}
export type LaboratoryMetricCellState =
| "unobserved"
| "ground-support"
| "nonground-occupied"
| "unknown-rejected";
export interface LaboratoryMetricCellEvidence {
centerBodyXyM: readonly [number, number];
zBoundsM: readonly [number | null, number | null];
state: LaboratoryMetricCellState;
}
export interface LaboratoryMetricLegendEntry {
id: LaboratoryMetricDecision | "context" | "local-surface" | "rolling" | "low-step";
id: LaboratoryMetricDecision
| LaboratoryMetricCellState
| "context"
| "local-surface"
| "rolling"
| "low-step";
label: string;
}
@@ -197,6 +214,9 @@ LaboratoryMetricEvidenceSceneHandle,
pointSemanticClassIds?: readonly (number | null)[];
semanticClasses?: readonly RecordedEvidenceSemanticClass[];
semanticPalette?: readonly RecordedEvidenceSemanticPaletteEntry[];
classifiedCells?: readonly LaboratoryMetricCellEvidence[];
classifiedCellSizeM?: number;
showClassifiedCells?: boolean;
}
>(function LaboratoryMetricEvidenceScene({
pointCloudBodyXyzM,
@@ -214,6 +234,9 @@ LaboratoryMetricEvidenceSceneHandle,
pointSemanticClassIds,
semanticClasses,
semanticPalette,
classifiedCells = [],
classifiedCellSizeM = 0.45,
showClassifiedCells = true,
}, ref) {
const hostRef = useRef<HTMLDivElement | null>(null);
const sceneRef = useRef<THREE.Scene | null>(null);
@@ -374,6 +397,63 @@ LaboratoryMetricEvidenceSceneHandle,
));
}
if (showClassifiedCells && classifiedCells.length) {
const cellsByState = new Map<LaboratoryMetricCellState, LaboratoryMetricCellEvidence[]>();
for (const cell of classifiedCells) {
const cells = cellsByState.get(cell.state) ?? [];
cells.push(cell);
cellsByState.set(cell.state, cells);
}
for (const [state, cells] of cellsByState) {
const color = state === "ground-support"
? tokenColor(host, "--nodedc-success-rgb", [181, 255, 90])
: state === "nonground-occupied"
? tokenColor(host, "--nodedc-danger-rgb", [255, 104, 112])
: state === "unknown-rejected"
? tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92])
: tokenColor(host, "--nodedc-text-muted", [96, 99, 106]);
const geometry = new THREE.BoxGeometry(
classifiedCellSizeM * 0.92,
1,
classifiedCellSizeM * 0.92,
);
const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: state === "unobserved" ? 0.035 : state === "ground-support" ? 0.12 : 0.24,
depthWrite: false,
});
const mesh = new THREE.InstancedMesh(geometry, material, cells.length);
const matrix = new THREE.Matrix4();
const scale = new THREE.Vector3(1, 1, 1);
const rotation = new THREE.Quaternion();
cells.forEach((cell, index) => {
const minimum = cell.zBoundsM[0];
const maximum = cell.zBoundsM[1];
const height = minimum === null || maximum === null
? 0.018
: Math.max(0.018, maximum - minimum);
const centerZ = minimum === null || maximum === null
? -0.012
: (minimum + maximum) / 2;
const [sceneX, sceneY, sceneZ] = scenePoint([
cell.centerBodyXyM[0],
cell.centerBodyXyM[1],
centerZ,
]);
scale.set(1, height, 1);
matrix.compose(
new THREE.Vector3(sceneX, sceneY, sceneZ),
rotation,
scale,
);
mesh.setMatrixAt(index, matrix);
});
mesh.instanceMatrix.needsUpdate = true;
content.add(mesh);
}
}
for (const obstacle of obstacles) {
if (
(
@@ -451,7 +531,10 @@ LaboratoryMetricEvidenceSceneHandle,
pointSemanticClassIds,
semanticClasses,
semanticPalette,
classifiedCells,
classifiedCellSizeM,
showCurrentIncrement,
showClassifiedCells,
showLocalSurface,
showRollingMap,
showLowStep,
@@ -563,7 +646,9 @@ LaboratoryMetricEvidenceSceneHandle,
});
})();
const metricLegendEntries = laboratoryMetricLegendEntries({
pointCloudCount: pointCloudBodyXyzM.length,
pointCloudCount: pointSemanticClassIds?.every((item) => item !== null)
? 0
: pointCloudBodyXyzM.length,
localSurfaceCount: localSurfaceBodyXyzM.length,
obstacles,
showCurrentIncrement,
@@ -571,6 +656,28 @@ LaboratoryMetricEvidenceSceneHandle,
showRollingMap,
showLowStep,
});
const classifiedLegendEntries = (() => {
if (!showClassifiedCells || !classifiedCells.length) return [];
const states = new Set(classifiedCells.map((cell) => cell.state));
return [
states.has("ground-support")
? { id: "ground-support" as const, label: "Ground support" }
: null,
states.has("nonground-occupied")
? { id: "nonground-occupied" as const, label: "Non-ground occupied" }
: null,
states.has("unknown-rejected")
? { id: "unknown-rejected" as const, label: "Unknown / rejected" }
: null,
states.has("unobserved")
? { id: "unobserved" as const, label: "Unobserved" }
: null,
]
.filter((entry): entry is NonNullable<typeof entry> => entry !== null)
.filter((entry) => !semanticLegendEntries.some(
(semanticEntry) => semanticEntry.label === entry.label,
));
})();
return (
<div className="laboratory-metric-evidence-scene">
@@ -581,6 +688,9 @@ LaboratoryMetricEvidenceSceneHandle,
{metricLegendEntries.map((entry) => (
<span key={entry.id} data-decision={entry.id}>{entry.label}</span>
))}
{classifiedLegendEntries.map((entry) => (
<span key={entry.id} data-decision={entry.id}>{entry.label}</span>
))}
{semanticLegendEntries.map((entry) => (
<span
key={entry.id}
@@ -46,6 +46,8 @@ import { fetchM48StaticOccupancyQualification } from "./m48StaticOccupancyQualif
import { fetchM48R3StaticOccupancyShadow } from "./m48r3StaticOccupancyShadow";
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
export type AdvancedLaboratoryWorkId =
| "m48-object-centric-quality"
@@ -54,6 +56,8 @@ export type AdvancedLaboratoryWorkId =
| "m48r3-static-occupancy-shadow"
| "m48s-fixed-class-detector"
| "m48t-risk-quality-temporal"
| "m49-tgs-fail-closed-evidence"
| "m49-tgs-full-shadow"
| "m47-reference-graph-shadow"
| "m4-replay-threat"
| "l3-pointpillars-visual-audit"
@@ -102,6 +106,8 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"m48r3-static-occupancy-shadow",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
"m47-reference-graph-shadow",
"m4-replay-threat",
"l3-pointpillars-visual-audit",
@@ -145,6 +151,8 @@ const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"m48r3-static-occupancy-shadow": "m48r3-static-occupancy-shadow",
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
"m49-tgs-fail-closed-evidence": "m49-tgs-fail-closed",
"m49-tgs-full-shadow": "m49-tgs-full-shadow",
"m47-reference-graph-shadow": "m47-reference-graph-lab",
"m4-replay-threat": "m4-threat-replay",
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
@@ -196,6 +204,8 @@ export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
m48r3StaticOccupancy: null,
m48s: null,
m48t: null,
m49Tgs: null,
m49TgsFull: null,
m4Threat: null,
l3: null,
l31: null,
@@ -326,6 +336,8 @@ export function advancedLaboratoryResultAvailable(
: workId === "m48r3-static-occupancy-shadow" ? results.m48r3StaticOccupancy !== null
: workId === "m48s-fixed-class-detector" ? results.m48s !== null
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
: workId === "m49-tgs-fail-closed-evidence" ? results.m49Tgs !== null
: workId === "m49-tgs-full-shadow" ? results.m49TgsFull !== null
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
: workId === "m4-replay-threat" ? results.m4Threat !== null
: workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
@@ -393,6 +405,12 @@ export async function fetchAdvancedLaboratoryResult(
} else if (workId === "m48t-risk-quality-temporal") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8T LAB identity не выбрана.");
results.m48t = await fetchM48TRiskQualityResult(resultId, { fetcher, signal });
} else if (workId === "m49-tgs-fail-closed-evidence") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 TGS LAB identity не выбрана.");
results.m49Tgs = await fetchM49TgsFailClosedResult(resultId, { fetcher, signal });
} else if (workId === "m49-tgs-full-shadow") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 full TGS shadow identity не выбрана.");
results.m49TgsFull = await fetchM49TgsFullShadowResult(resultId, { fetcher, signal });
} else if (workId === "m47-reference-graph-shadow") {
if (!resultId) {
throw new AdvancedLaboratoryContractError("M4.7 LAB identity не выбрана.");
@@ -40,6 +40,8 @@ import type { M48StaticOccupancyQualificationResult } from "./m48StaticOccupancy
import type { M48R3StaticOccupancyShadowResult } from "./m48r3StaticOccupancyShadow";
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
import type { M49TgsFailClosedResult } from "./m49TgsFailClosed";
import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
export interface AdvancedLaboratoryResults {
m47Graph: M47ReferenceGraphLabResult | null;
@@ -49,6 +51,8 @@ export interface AdvancedLaboratoryResults {
m48r3StaticOccupancy: M48R3StaticOccupancyShadowResult | null;
m48s: M48SFixedClassDetectorResult | null;
m48t: M48TRiskQualityResult | null;
m49Tgs: M49TgsFailClosedResult | null;
m49TgsFull: M49TgsFullShadowResult | null;
m4Threat: M4ThreatReplayResult | null;
l3: L3PointPillarsVisualAuditResult | null;
l31: L31PointPillarsRavnovesResult | null;
@@ -969,7 +969,7 @@ export async function fetchAdvancedLaboratoryResults({
return {
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
m48s: null, m48t: null, m4Threat: null,
m48s: null, m48t: null, m49Tgs: null, m49TgsFull: null, m4Threat: null,
l3: null, l31: null, l32: null, l33: null,
e31,
e32,
@@ -0,0 +1,349 @@
import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^m49-tgs-fail-closed-[a-f0-9]{64}$/;
export type M49TgsProfile = "current_increment" | "causal_rolling_1s";
export type M49TgsStateCode = 0 | 1 | 2 | 3;
export type M49TgsPointStateCode = 1 | 2 | 3;
export interface M49TgsAnchorSummary {
anchorFrameIndex: number;
slot: number;
pointCount: number;
groundPointCount: number;
nongroundPointCount: number;
rejectedPointCount: number;
groundCellCount: number;
nongroundCellCount: number;
rejectedCellCount: number;
unobservedCellCount: number;
allPointsAccounted: true;
}
export interface M49TgsFailClosedResult {
resultId: string;
createdAtUtc: string;
source: {
sourceId: "RAVNOVES00";
sourceSessionId: "20260720T065719Z_viewer_live";
sourcePackSha256: string;
linkedVisualResultId: string;
anchorFrameIndices: readonly number[];
};
configuration: {
profileId: string;
configSha256: string;
coordinateFrame: "map-gravity-local";
primaryProfile: "causal_rolling_1s";
cellSizeM: number;
radiusM: number;
};
execution: {
worker: "Worker 006";
device: "cpu";
gpuUsed: false;
wrapperElapsedSeconds: number;
};
metrics: {
anchorCount: number;
anchorProfileCount: number;
allEligiblePointsAccounted: true;
costmapCellCount: number;
processWallCurrentP50Ms: number;
processWallCurrentMaxMs: number;
processWallRollingP50Ms: number;
processWallRollingMaxMs: number;
processMaxRssKib: number;
primary: readonly M49TgsAnchorSummary[];
};
acceptance: {
representationComplete: true;
allPointsAccounted: true;
aosAbsent: true;
gpuAbsent: true;
visualQualityAccepted: false;
traversabilityAccepted: false;
};
decision: {
state: "visual-review-required";
candidateRetained: true;
nextAction: string;
};
limitations: readonly string[];
}
export interface M49TgsAnchorSpatial {
resultId: string;
anchorFrameIndex: number;
sourceSequence: number;
profile: M49TgsProfile;
coordinateFrame: "map-gravity-local";
pointsXyzM: readonly (readonly [number, number, number])[];
pointStates: readonly M49TgsPointStateCode[];
costmap: {
cellSizeM: number;
radiusM: number;
centersXyM: readonly (readonly [number, number])[];
states: readonly M49TgsStateCode[];
zBoundsM: readonly (readonly [number | null, number | null])[];
};
allPointsAccounted: true;
aosUsed: false;
}
export class M49TgsContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new M49TgsContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function text(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new M49TgsContractError(`${label}: ожидалась строка.`);
}
return value;
}
function exact<T extends string | boolean>(value: unknown, expected: T, label: string): T {
if (value !== expected) throw new M49TgsContractError(`${label}: нарушен контракт.`);
return expected;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new M49TgsContractError(`${label}: ожидалось число.`);
}
return value;
}
function integer(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new M49TgsContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function sha256(value: unknown, label: string): string {
const parsed = text(value, label);
if (!/^[a-f0-9]{64}$/.test(parsed)) {
throw new M49TgsContractError(`${label}: неверный SHA-256.`);
}
return parsed;
}
function resultId(value: unknown): string {
const parsed = text(value, "M49 result id");
if (!RESULT_ID.test(parsed)) throw new M49TgsContractError("M49 identity недопустима.");
return parsed;
}
function numbers(value: unknown, size: number, label: string): number[] {
if (!Array.isArray(value) || value.length !== size) {
throw new M49TgsContractError(`${label}: неверная размерность.`);
}
return value.map((item) => numberValue(item, label));
}
function anchor(value: unknown, label: string): M49TgsAnchorSummary {
const row = objectValue(value, label);
exact(row.profile_id, "causal_rolling_1s", `${label}.profile`);
exact(row.all_points_accounted, true, `${label}.accounting`);
const parsed: M49TgsAnchorSummary = {
anchorFrameIndex: integer(row.anchor_frame_index, `${label}.anchor`),
slot: integer(row.slot, `${label}.slot`),
pointCount: integer(row.point_count, `${label}.points`),
groundPointCount: integer(row.ground_point_count, `${label}.ground points`),
nongroundPointCount: integer(row.nonground_point_count, `${label}.nonground points`),
rejectedPointCount: integer(row.rejected_point_count, `${label}.rejected points`),
groundCellCount: integer(row.ground_cell_count, `${label}.ground cells`),
nongroundCellCount: integer(row.nonground_cell_count, `${label}.nonground cells`),
rejectedCellCount: integer(row.rejected_cell_count, `${label}.rejected cells`),
unobservedCellCount: integer(row.unobserved_cell_count, `${label}.unobserved cells`),
allPointsAccounted: true,
};
if (
parsed.groundPointCount + parsed.nongroundPointCount + parsed.rejectedPointCount
!== parsed.pointCount
|| parsed.groundCellCount + parsed.nongroundCellCount
+ parsed.rejectedCellCount + parsed.unobservedCellCount !== 2244
) {
throw new M49TgsContractError(`${label}: fail-closed accounting нарушен.`);
}
return parsed;
}
export async function fetchM49TgsFailClosedResult(
id: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFailClosedResult> {
if (!RESULT_ID.test(id)) throw new M49TgsContractError("M49 identity недопустима.");
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-fail-closed/${encodeURIComponent(id)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsContractError(`M49 TGS недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 TGS");
exact(payload.schema_version, "missioncore.m49-tgs-fail-closed-view/v1", "M49 schema");
exact(payload.result_id, id, "M49 result");
exact(payload.ground_truth, false, "M49 ground truth");
exact(payload.access, "read-only", "M49 access");
const source = objectValue(payload.source, "M49 source");
const configuration = objectValue(payload.configuration, "M49 configuration");
const execution = objectValue(payload.execution, "M49 execution");
const metrics = objectValue(payload.metrics, "M49 metrics");
const acceptance = objectValue(payload.acceptance, "M49 acceptance");
const decision = objectValue(payload.decision, "M49 decision");
if (!Array.isArray(source.anchor_frame_indices) || !Array.isArray(metrics.primary)) {
throw new M49TgsContractError("M49 anchors: ожидался массив.");
}
if (!Array.isArray(payload.limitations)) {
throw new M49TgsContractError("M49 limitations: ожидался массив.");
}
const anchorFrameIndices = source.anchor_frame_indices.map(
(item, index) => integer(item, `M49 anchor ${index}`),
);
const primary = metrics.primary.map((item, index) => anchor(item, `M49 primary ${index}`));
if (
anchorFrameIndices.length !== 10
|| new Set(anchorFrameIndices).size !== 10
|| primary.length !== 10
|| integer(metrics.anchor_count, "M49 anchor count") !== 10
|| integer(metrics.anchor_profile_count, "M49 profile count") !== 20
|| integer(metrics.costmap_cell_count, "M49 costmap cells") !== 2244
|| primary.some((item, index) => item.anchorFrameIndex !== anchorFrameIndices[index])
) {
throw new M49TgsContractError("M49 anchor set или costmap contract нарушен.");
}
return {
resultId: resultId(payload.result_id),
createdAtUtc: text(payload.created_at_utc, "M49 created"),
source: {
sourceId: exact(source.source_id, "RAVNOVES00", "M49 source id"),
sourceSessionId: exact(
source.source_session_id,
"20260720T065719Z_viewer_live",
"M49 source session",
),
sourcePackSha256: sha256(source.source_pack_sha256, "M49 source pack"),
linkedVisualResultId: text(source.linked_visual_result_id, "M49 visual result"),
anchorFrameIndices,
},
configuration: {
profileId: text(configuration.profile_id, "M49 profile"),
configSha256: sha256(configuration.config_sha256, "M49 config"),
coordinateFrame: exact(configuration.coordinate_frame, "map-gravity-local", "M49 frame"),
primaryProfile: exact(configuration.primary_profile, "causal_rolling_1s", "M49 primary"),
cellSizeM: numberValue(configuration.cell_size_m, "M49 cell size"),
radiusM: numberValue(configuration.radius_m, "M49 radius"),
},
execution: {
worker: exact(execution.worker, "Worker 006", "M49 worker"),
device: exact(execution.device, "cpu", "M49 device"),
gpuUsed: exact(execution.gpu_used, false, "M49 GPU"),
wrapperElapsedSeconds: numberValue(execution.wrapper_elapsed_seconds, "M49 wall"),
},
metrics: {
anchorCount: 10,
anchorProfileCount: 20,
allEligiblePointsAccounted: exact(metrics.all_eligible_points_accounted, true, "M49 accounting"),
costmapCellCount: 2244,
processWallCurrentP50Ms: numberValue(metrics.process_wall_current_p50_ms, "M49 current p50"),
processWallCurrentMaxMs: numberValue(metrics.process_wall_current_max_ms, "M49 current max"),
processWallRollingP50Ms: numberValue(metrics.process_wall_rolling_p50_ms, "M49 rolling p50"),
processWallRollingMaxMs: numberValue(metrics.process_wall_rolling_max_ms, "M49 rolling max"),
processMaxRssKib: integer(metrics.process_max_rss_kib, "M49 RSS"),
primary,
},
acceptance: {
representationComplete: exact(acceptance.representation_complete, true, "M49 representation"),
allPointsAccounted: exact(acceptance.all_points_accounted, true, "M49 points"),
aosAbsent: exact(acceptance.aos_absent, true, "M49 AOS"),
gpuAbsent: exact(acceptance.gpu_absent, true, "M49 GPU absent"),
visualQualityAccepted: exact(acceptance.visual_quality_accepted, false, "M49 visual quality"),
traversabilityAccepted: exact(acceptance.traversability_accepted, false, "M49 traversability"),
},
decision: {
state: exact(decision.state, "visual-review-required", "M49 decision"),
candidateRetained: exact(decision.candidate_retained, true, "M49 retained"),
nextAction: text(decision.next_action, "M49 next action"),
},
limitations: payload.limitations.map((item, index) => text(item, `M49 limitation ${index}`)),
};
}
export async function fetchM49TgsAnchorSpatial(
id: string,
anchorFrameIndex: number,
profile: M49TgsProfile = "causal_rolling_1s",
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsAnchorSpatial> {
if (!RESULT_ID.test(id) || !Number.isSafeInteger(anchorFrameIndex) || anchorFrameIndex < 0) {
throw new M49TgsContractError("M49 anchor identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-fail-closed/${encodeURIComponent(id)}/anchors/${anchorFrameIndex}/spatial?profile=${encodeURIComponent(profile)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsContractError(`M49 anchor недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 anchor");
exact(payload.schema_version, "missioncore.m49-tgs-anchor-spatial/v1", "M49 anchor schema");
exact(payload.result_id, id, "M49 anchor result");
exact(payload.coordinate_frame, "map-gravity-local", "M49 anchor frame");
exact(payload.all_points_accounted, true, "M49 anchor accounting");
exact(payload.aos_used, false, "M49 anchor AOS");
if (!Array.isArray(payload.points_xyz_m) || !Array.isArray(payload.point_states)) {
throw new M49TgsContractError("M49 points: ожидался массив.");
}
const costmap = objectValue(payload.costmap, "M49 costmap");
if (!Array.isArray(costmap.centers_xy_m) || !Array.isArray(costmap.states) || !Array.isArray(costmap.z_bounds_m)) {
throw new M49TgsContractError("M49 costmap arrays: нарушен контракт.");
}
const points = payload.points_xyz_m.map((item, index) => numbers(item, 3, `M49 point ${index}`) as [number, number, number]);
const pointStates = payload.point_states.map((item, index) => {
const state = integer(item, `M49 point state ${index}`);
if (state !== 1 && state !== 2 && state !== 3) throw new M49TgsContractError("M49 point state неизвестен.");
return state;
});
if (points.length !== pointStates.length) throw new M49TgsContractError("M49 point accounting нарушен.");
const centers = costmap.centers_xy_m.map((item, index) => numbers(item, 2, `M49 cell ${index}`) as [number, number]);
const states = costmap.states.map((item, index) => {
const state = integer(item, `M49 cell state ${index}`);
if (state !== 0 && state !== 1 && state !== 2 && state !== 3) throw new M49TgsContractError("M49 cell state неизвестен.");
return state;
});
const zBounds = costmap.z_bounds_m.map((item, index) => {
if (!Array.isArray(item) || item.length !== 2) throw new M49TgsContractError(`M49 z ${index}: размерность.`);
return item.map((value) => value === null ? null : numberValue(value, `M49 z ${index}`)) as [number | null, number | null];
});
if (
centers.length !== 2244
|| centers.length !== states.length
|| centers.length !== zBounds.length
|| integer(payload.anchor_frame_index, "M49 anchor frame") !== anchorFrameIndex
|| integer(payload.source_sequence, "M49 source sequence") !== anchorFrameIndex
) {
throw new M49TgsContractError("M49 costmap accounting нарушен.");
}
return {
resultId: id,
anchorFrameIndex,
sourceSequence: anchorFrameIndex,
profile: exact(payload.profile, profile, "M49 profile"),
coordinateFrame: "map-gravity-local",
pointsXyzM: points,
pointStates,
costmap: {
cellSizeM: numberValue(costmap.cell_size_m, "M49 cell size"),
radiusM: numberValue(costmap.radius_m, "M49 radius"),
centersXyM: centers,
states,
zBoundsM: zBounds,
},
allPointsAccounted: true,
aosUsed: false,
};
}
@@ -0,0 +1,337 @@
import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^m49-tgs-full-shadow-[a-f0-9]{64}$/;
const SEMANTIC_RESULT_ID = /^e47-semantic-slam-[a-f0-9]{64}$/;
export type M49TgsFullShadowStateCode = 0 | 1 | 2 | 3;
export interface M49TgsFullShadowResult {
resultId: string;
createdAtUtc: string;
source: {
sourcePackSha256: string;
linkedVisualResultId: string;
linkedSemanticResultId: string;
};
configuration: {
configSha256: string;
cellSizeM: number;
radiusM: number;
historySeconds: number;
};
execution: {
wrapperElapsedSeconds: number;
};
timeline: {
frameCount: 4489;
availableLidarFrameCount: 3928;
missingLidarFrameCount: 561;
durationSeconds: number;
effectiveFps: number;
};
pointAccounting: {
eligible: number;
ground: number;
nonground: number;
rejected: number;
unaccounted: 0;
};
performance: {
candidateTgsMs: { p50: number; p95: number; p99: number; max: number };
completionAgeMs: { p50: number; p95: number; p99: number; max: number };
capacityDropCount: number;
};
acceptance: {
representationComplete: true;
visualQualityAccepted: false;
integratedGraphPerformanceAccepted: false;
allFramesAccounted: boolean;
allEligiblePointsAccounted: boolean;
};
decision: {
state: string;
candidateRetained: boolean;
nextAction: string;
};
}
export interface M49TgsFullShadowSpatial {
resultId: string;
sourceSequence: number;
sourceFrameIndex: number;
sessionSeconds: number;
sampleAvailable: boolean;
costmap: {
cellSizeM: number;
radiusM: number;
centersXyM: readonly (readonly [number, number])[];
states: readonly M49TgsFullShadowStateCode[];
zBoundsM: readonly (readonly [number | null, number | null])[];
};
metrics: {
eligiblePointCount: number;
groundPointCount: number;
nongroundPointCount: number;
rejectedPointCount: number;
occupiedCellCount: number;
};
}
export interface M49TgsFullShadowSpatialChunk {
resultId: string;
start: number;
count: number;
frames: readonly M49TgsFullShadowSpatial[];
}
export class M49TgsFullShadowContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new M49TgsFullShadowContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function text(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new M49TgsFullShadowContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new M49TgsFullShadowContractError(`${label}: ожидалось число.`);
}
return value;
}
function integer(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new M49TgsFullShadowContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function booleanValue(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new M49TgsFullShadowContractError(`${label}: ожидался boolean.`);
}
return value;
}
function exact<T extends string | boolean | number>(value: unknown, expected: T, label: string): T {
if (value !== expected) throw new M49TgsFullShadowContractError(`${label}: нарушен контракт.`);
return expected;
}
function timing(value: unknown, label: string) {
const row = objectValue(value, label);
return {
p50: numberValue(row.p50, `${label}.p50`),
p95: numberValue(row.p95, `${label}.p95`),
p99: numberValue(row.p99, `${label}.p99`),
max: numberValue(row.max, `${label}.max`),
};
}
export async function fetchM49TgsFullShadowResult(
id: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowResult> {
if (!RESULT_ID.test(id)) throw new M49TgsFullShadowContractError("M49 full-shadow identity недопустима.");
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full shadow недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full shadow");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-view/v1", "M49 full schema");
exact(payload.result_id, id, "M49 full result");
exact(payload.ground_truth, false, "M49 full ground truth");
exact(payload.access, "read-only", "M49 full access");
const source = objectValue(payload.source, "M49 full source");
const configuration = objectValue(payload.configuration, "M49 full configuration");
const execution = objectValue(payload.execution, "M49 full execution");
const timeline = objectValue(payload.timeline, "M49 full timeline");
const accounting = objectValue(payload.point_accounting, "M49 full accounting");
const performance = objectValue(payload.performance, "M49 full performance");
const acceptance = objectValue(payload.acceptance, "M49 full acceptance");
const decision = objectValue(payload.decision, "M49 full decision");
const linkedSemanticResultId = text(
source.linked_semantic_result_id,
"M49 full semantic result",
);
if (!SEMANTIC_RESULT_ID.test(linkedSemanticResultId)) {
throw new M49TgsFullShadowContractError("M49 full semantic result: нарушена идентичность.");
}
return {
resultId: text(payload.result_id, "M49 full result"),
createdAtUtc: text(payload.created_at_utc, "M49 full created"),
source: {
sourcePackSha256: text(source.source_pack_sha256, "M49 full source pack"),
linkedVisualResultId: text(source.linked_visual_result_id, "M49 full visual result"),
linkedSemanticResultId,
},
configuration: {
configSha256: text(configuration.config_sha256, "M49 full config"),
cellSizeM: numberValue(configuration.cell_size_m, "M49 full cell size"),
radiusM: numberValue(configuration.radius_m, "M49 full radius"),
historySeconds: numberValue(configuration.history_seconds, "M49 full history"),
},
execution: { wrapperElapsedSeconds: numberValue(execution.wrapper_elapsed_seconds, "M49 full wall") },
timeline: {
frameCount: exact(integer(timeline.frame_count, "M49 full frames"), 4489, "M49 full frames"),
availableLidarFrameCount: exact(integer(timeline.available_lidar_frame_count, "M49 full available"), 3928, "M49 full available"),
missingLidarFrameCount: exact(integer(timeline.missing_lidar_frame_count, "M49 full missing"), 561, "M49 full missing"),
durationSeconds: numberValue(timeline.duration_seconds, "M49 full duration"),
effectiveFps: numberValue(timeline.effective_fps, "M49 full fps"),
},
pointAccounting: {
eligible: integer(accounting.eligible, "M49 full eligible"),
ground: integer(accounting.ground, "M49 full ground"),
nonground: integer(accounting.nonground, "M49 full nonground"),
rejected: integer(accounting.rejected, "M49 full rejected"),
unaccounted: exact(integer(accounting.unaccounted, "M49 full unaccounted"), 0, "M49 full unaccounted"),
},
performance: {
candidateTgsMs: timing(performance.candidate_tgs_ms, "M49 full TGS"),
completionAgeMs: timing(performance.completion_age_ms, "M49 full completion"),
capacityDropCount: integer(performance.capacity_drop_count, "M49 full drops"),
},
acceptance: {
representationComplete: exact(acceptance.representation_complete, true, "M49 full representation"),
visualQualityAccepted: exact(acceptance.visual_quality_accepted, false, "M49 full visual"),
integratedGraphPerformanceAccepted: exact(acceptance.integrated_graph_performance_accepted, false, "M49 full integrated"),
allFramesAccounted: booleanValue(acceptance.all_frames_accounted, "M49 full frame accounting"),
allEligiblePointsAccounted: booleanValue(acceptance.all_eligible_points_accounted, "M49 full point accounting"),
},
decision: {
state: text(decision.state, "M49 full decision"),
candidateRetained: booleanValue(decision.candidate_retained, "M49 full retained"),
nextAction: text(decision.next_action, "M49 full next action"),
},
};
}
function pair(value: unknown, label: string): readonly [number, number] {
if (!Array.isArray(value) || value.length !== 2) {
throw new M49TgsFullShadowContractError(`${label}: неверная размерность.`);
}
return [numberValue(value[0], label), numberValue(value[1], label)];
}
function parseSpatial(
id: string,
sourceSequence: number,
payload: Record<string, unknown>,
sharedCostmap?: Record<string, unknown>,
): M49TgsFullShadowSpatial {
exact(payload.source_sequence, sourceSequence, "M49 full spatial sequence");
const costmap = sharedCostmap ?? objectValue(payload.costmap, "M49 full costmap");
const metrics = objectValue(payload.metrics, "M49 full frame metrics");
const centers = costmap.centers_xy_m;
const statesRaw = sharedCostmap ? payload.states : costmap.states;
const zBoundsRaw = sharedCostmap ? payload.z_bounds_m : costmap.z_bounds_m;
if (!Array.isArray(centers) || !Array.isArray(statesRaw) || !Array.isArray(zBoundsRaw)
|| centers.length !== 2244 || statesRaw.length !== 2244 || zBoundsRaw.length !== 2244) {
throw new M49TgsFullShadowContractError("M49 full costmap: неверная размерность.");
}
const states = statesRaw.map((value, index) => {
const parsed = integer(value, `M49 full state ${index}`);
if (parsed > 3) throw new M49TgsFullShadowContractError("M49 full state недопустим.");
return parsed as M49TgsFullShadowStateCode;
});
const zBounds = zBoundsRaw.map((value, index) => {
if (!Array.isArray(value) || value.length !== 2) {
throw new M49TgsFullShadowContractError(`M49 full z ${index}: неверная размерность.`);
}
return value.map((item) => item === null
? null
: numberValue(item, `M49 full z ${index}`)) as [number | null, number | null];
});
const sampleAvailable = booleanValue(payload.sample_available, "M49 full sample available");
if (!sampleAvailable && states.some((state) => state !== 0)) {
throw new M49TgsFullShadowContractError("M49 missing LiDAR frame не остался UNOBSERVED.");
}
return {
resultId: id,
sourceSequence,
sourceFrameIndex: integer(payload.source_frame_index, "M49 full source frame"),
sessionSeconds: numberValue(payload.session_seconds, "M49 full session seconds"),
sampleAvailable,
costmap: {
cellSizeM: numberValue(costmap.cell_size_m, "M49 full cell size"),
radiusM: numberValue(costmap.radius_m, "M49 full radius"),
centersXyM: centers.map((value, index) => pair(value, `M49 full center ${index}`)),
states,
zBoundsM: zBounds,
},
metrics: {
eligiblePointCount: integer(metrics.eligible_point_count, "M49 full eligible"),
groundPointCount: integer(metrics.ground_point_count, "M49 full ground"),
nongroundPointCount: integer(metrics.nonground_point_count, "M49 full nonground"),
rejectedPointCount: integer(metrics.rejected_point_count, "M49 full rejected"),
occupiedCellCount: integer(metrics.occupied_cell_count, "M49 full occupied cells"),
},
};
}
export async function fetchM49TgsFullShadowSpatial(
id: string,
sourceSequence: number,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowSpatial> {
if (!RESULT_ID.test(id) || !Number.isSafeInteger(sourceSequence) || sourceSequence < 0 || sourceSequence >= 4489) {
throw new M49TgsFullShadowContractError("M49 full-shadow frame identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}/frames/${sourceSequence}/spatial`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full-shadow frame недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full spatial");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-spatial/v1", "M49 full spatial schema");
exact(payload.result_id, id, "M49 full spatial result");
return parseSpatial(id, sourceSequence, payload);
}
export async function fetchM49TgsFullShadowSpatialChunk(
id: string,
start: number,
count = 24,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowSpatialChunk> {
if (!RESULT_ID.test(id) || !Number.isSafeInteger(start) || start < 0 || start >= 4489
|| !Number.isSafeInteger(count) || count < 1 || count > 24) {
throw new M49TgsFullShadowContractError("M49 full-shadow chunk identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}/spatial/chunk?start=${start}&count=${count}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full-shadow chunk недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full spatial chunk");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-spatial-chunk/v1", "M49 full chunk schema");
exact(payload.result_id, id, "M49 full chunk result");
exact(payload.start, start, "M49 full chunk start");
const returnedCount = integer(payload.count, "M49 full chunk count");
if (!Array.isArray(payload.frames) || payload.frames.length !== returnedCount
|| returnedCount !== Math.min(count, 4489 - start)) {
throw new M49TgsFullShadowContractError("M49 full chunk: неверная размерность.");
}
const costmap = objectValue(payload.costmap, "M49 full chunk costmap");
return {
resultId: id,
start,
count: returnedCount,
frames: payload.frames.map((value, index) => parseSpatial(
id,
start + index,
objectValue(value, `M49 full chunk frame ${index}`),
costmap,
)),
};
}
@@ -91,6 +91,11 @@
justify-content: space-between;
}
.m4-replay-threat-evidence-viewer[data-mode-controls="content"]
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="spatial"] {
right: 3.8rem;
}
.m4-replay-threat-visual__pane-toolbar > *,
.m4-replay-threat-visual__spatial-toolbar-end > * {
pointer-events: auto;
@@ -350,3 +355,20 @@
background: rgb(var(--nodedc-accent-rgb));
opacity: 0.72;
}
.laboratory-metric-evidence-scene__legend span[data-decision="ground-support"]::before {
background: rgb(var(--nodedc-success-rgb));
}
.laboratory-metric-evidence-scene__legend span[data-decision="nonground-occupied"]::before {
background: rgb(var(--nodedc-danger-rgb));
}
.laboratory-metric-evidence-scene__legend span[data-decision="unknown-rejected"]::before {
background: rgb(var(--nodedc-warning-rgb));
}
.laboratory-metric-evidence-scene__legend span[data-decision="unobserved"]::before {
background: var(--nodedc-text-muted);
opacity: 0.42;
}
@@ -48,6 +48,8 @@ import { M48StaticOccupancyQualificationResultView } from "./M48StaticOccupancyQ
import { M48R3StaticOccupancyShadowResultView } from "./M48R3StaticOccupancyShadowResult";
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
@@ -108,6 +110,12 @@ export function AdvancedLaboratoryResult({
if (workId === "m48t-risk-quality-temporal" && results.m48t) {
return <M48TRiskQualityResultView rigLabel={rigLabel} result={results.m48t} />;
}
if (workId === "m49-tgs-fail-closed-evidence" && results.m49Tgs) {
return <M49TgsFailClosedResultView rigLabel={rigLabel} result={results.m49Tgs} />;
}
if (workId === "m49-tgs-full-shadow" && results.m49TgsFull) {
return <M49TgsFullShadowResultView rigLabel={rigLabel} result={results.m49TgsFull} />;
}
if (workId === "m47-reference-graph-shadow" && results.m47Graph) {
return <M47ReferenceGraphResultView rigLabel={rigLabel} result={results.m47Graph} />;
}
@@ -0,0 +1,152 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
RecordedEvidenceSemanticClass,
RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
fetchM49TgsAnchorSpatial,
type M49TgsAnchorSpatial,
type M49TgsFailClosedResult,
type M49TgsStateCode,
} from "../../core/laboratory/m49TgsFailClosed";
import {
M4ReplayThreatVisual,
type M4ReplayClassifiedSpatialFrame,
type M4ReplayThreatReviewAnchor,
} from "./M4ReplayThreatVisual";
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
{ id: 1, label: "Ground support" },
{ id: 2, label: "Non-ground occupied" },
{ id: 3, label: "Unknown / rejected" },
];
const PALETTE: readonly RecordedEvidenceSemanticPaletteEntry[] = [
{ classId: 1, color: { kind: "token", token: "--nodedc-success-rgb" } },
{ classId: 2, color: { kind: "token", token: "--nodedc-danger-rgb" } },
{ classId: 3, color: { kind: "token", token: "--nodedc-warning-rgb" } },
];
function cellState(code: M49TgsStateCode): M4ReplayClassifiedSpatialFrame["cellsMapGravityLocal"][number]["state"] {
if (code === 1) return "ground-support";
if (code === 2) return "nonground-occupied";
if (code === 3) return "unknown-rejected";
return "unobserved";
}
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "M49 TGS spatial evidence недоступно.";
}
export function M49TgsFailClosedEvidence({
result,
}: {
result: M49TgsFailClosedResult;
}) {
const [activeSequence, setActiveSequence] = useState<number | null>(null);
const [spatial, setSpatial] = useState<M49TgsAnchorSpatial | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const spatialCacheRef = useRef(new Map<string, M49TgsAnchorSpatial>());
const anchorSequences = useMemo(
() => new Set(result.metrics.primary.map((item) => item.anchorFrameIndex)),
[result.metrics.primary],
);
const expectedAtSequence = activeSequence !== null && anchorSequences.has(activeSequence);
useEffect(() => {
if (activeSequence === null || !anchorSequences.has(activeSequence)) {
setSpatial(null);
setLoading(false);
setError(null);
return;
}
const cacheKey = `${result.resultId}:${activeSequence}`;
const cached = spatialCacheRef.current.get(cacheKey);
if (cached) {
setSpatial(cached);
setLoading(false);
setError(null);
return;
}
const controller = new AbortController();
setSpatial(null);
setLoading(true);
setError(null);
void fetchM49TgsAnchorSpatial(
result.resultId,
activeSequence,
"causal_rolling_1s",
{ signal: controller.signal },
)
.then((next) => {
if (!controller.signal.aborted) {
spatialCacheRef.current.set(cacheKey, next);
setSpatial(next);
}
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setError(message(caught));
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [activeSequence, anchorSequences, result.resultId]);
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(
() => result.metrics.primary.map((item) => ({
id: `m49-tgs-${item.anchorFrameIndex}`,
sourceSequence: item.anchorFrameIndex,
extentXyxyNormalized: [0, 0, 0, 0],
matchedAtThreshold: false,
statusLabel: "визуальная проверка",
})),
[result.metrics.primary],
);
const classifiedFrame = useMemo<M4ReplayClassifiedSpatialFrame | null>(() => {
if (!spatial) return null;
return {
sourceSequence: spatial.sourceSequence,
pointsMapGravityLocalXyzM: spatial.pointsXyzM,
pointClassIds: spatial.pointStates,
cellsMapGravityLocal: spatial.costmap.centersXyM.map((center, index) => ({
centerXyM: center,
zBoundsM: spatial.costmap.zBoundsM[index]!,
state: cellState(spatial.costmap.states[index]!),
})),
cellSizeM: spatial.costmap.cellSizeM,
classes: CLASSES,
palette: PALETTE,
};
}, [spatial]);
const handleSequenceChange = useCallback((sequence: number | null) => {
setActiveSequence(sequence);
}, []);
return (
<M4ReplayThreatVisual
resultId={result.source.linkedVisualResultId}
reviewAnchors={reviewAnchors}
showReviewAnchorBoxes={false}
reviewLabel="10 gravity-aligned TGS anchors"
evidenceLabel="M49 · TGS fail-closed"
initialSpatialMode="3d"
onActiveSequenceChange={handleSequenceChange}
classifiedSpatialLayer={{
label: "TGS fail-closed · causal rolling 1 s",
pointLayerLabel: "TGS POINTS",
cellLayerLabel: "COSTMAP",
expectedAtSequence,
frame: classifiedFrame,
loading,
error,
}}
/>
);
}
@@ -0,0 +1,92 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M49TgsFailClosedResult } from "../../core/laboratory/m49TgsFailClosed";
import { M49TgsFailClosedEvidence } from "./M49TgsFailClosedEvidence";
function number(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
export function M49TgsFailClosedResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: M49TgsFailClosedResult;
}) {
const worst = [...result.metrics.primary].sort(
(left, right) => (
right.nongroundPointCount / Math.max(right.pointCount, 1)
- left.nongroundPointCount / Math.max(left.pointCount, 1)
),
)[0]!;
const status = "Representation complete; визуальное качество ещё не принято";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.9T4 · TRAVEL TGS fail-closed evidence"
description="TRAVEL GroundSeg запущен без AOS на десяти immutable RAVNOVES00 anchors. Вход сохранён в gravity-aligned map frame; каждый eligible point получил состояние, а каждая costmap-ячейка остаётся ground, occupied, rejected или unobserved."
status={status}
statusTone="warning"
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · Camera + gravity-aligned LiDAR · 10 anchors` },
{ label: "Метод", value: "TRAVEL TGS only · AOS OFF · causal rolling 1 s" },
{ label: "Evidence", value: `${result.metrics.anchorCount} anchors · ${result.metrics.costmapCellCount.toLocaleString("ru-RU")} cells/anchor · all points accounted` },
{ label: "Нагрузка", value: `Worker 006 CPU-only · GPU 0 · wrapper ${number(result.execution.wrapperElapsedSeconds, 2)} с` },
{ label: "Authority", value: "REPLAY-SIMULATED · visual/traversability/navigation/actuation OFF" },
]}
brief={{
question: "Отделяет ли готовый TRAVEL TGS опорную поверхность от неизвестной занятой геометрии достаточно чисто, чтобы заменить самодельный static-obstacle threshold pipeline?",
approach: "На десяти сложных кадрах проверяется полный gravity-aligned point set и fail-closed costmap. Зелёное — опора, красное — non-ground occupied, жёлтое — rejected/unknown, тёмное — unobserved; камера остаётся синхронным первичным контекстом.",
principalResult: `Контракт представления закрыт: ${result.metrics.anchorProfileCount}/20 профилей, ни одной потерянной eligible point, AOS и GPU отсутствуют. Process wall rolling p50/max: ${number(result.metrics.processWallRollingP50Ms, 0)}/${number(result.metrics.processWallRollingMaxMs, 0)} мс.`,
limitation: `Качество не принято: особенно проверить кадр ${worst.anchorFrameIndex + 1}, где ${number(worst.nongroundPointCount / Math.max(worst.pointCount, 1) * 100)}% rolling points помечены non-ground. Это может быть реальная боковая геометрия либо ложная блокировка поверхности.`,
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: "travel-tgs-gravity-aligned-fail-closed/v1",
components: [
{ kind: "source", name: "RAVNOVES00", version: "10 immutable anchors", role: "camera + registered map increments", identitySha256: result.source.sourcePackSha256 },
{ kind: "algorithm", name: "TRAVEL GroundSeg", version: "95dc2fbd66a343efd9060c45a5711b6307a950a4", role: "ground/nonground separation; AOS excluded", identitySha256: result.configuration.configSha256 },
{ kind: "algorithm", name: "fail-closed complement adapter", version: "v1", role: "explicit rejected points and unobserved cells", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.9T4 VISUAL EVIDENCE · CAMERA + GRAVITY-ALIGNED TGS"
title="10 anchors: полный TGS point set и четырёхсостояний costmap на том же recorded timeline"
kind="recorded-replay"
resizable
>
<M49TgsFailClosedEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Что уже доказано и что проверяем глазами"
status={status}
statusTone="warning"
metrics={[
{ label: "Point accounting", value: "100%", hint: "ground + non-ground + rejected = exact eligible input" },
{ label: "Anchors", value: `${result.metrics.anchorCount}/10`, hint: "current + causal rolling 1 s" },
{ label: "Costmap", value: `${result.metrics.costmapCellCount.toLocaleString("ru-RU")} cells`, hint: `${number(result.configuration.cellSizeM, 2)} м · radius ${number(result.configuration.radiusM, 0)} м` },
{ label: "Process wall rolling", value: `${number(result.metrics.processWallRollingP50Ms, 0)} / ${number(result.metrics.processWallRollingMaxMs, 0)} мс`, hint: "p50 / max · CPU process envelope, не realtime integration" },
{ label: "GPU / AOS", value: "0 / OFF", hint: "Worker 006; Frigate budget не затронут" },
]}
conclusion={{
proved: "Готовый TGS можно встроить fail-closed: исходные точки не теряются, unknown не становится free, AOS не нужен, а вычисление укладывается в лёгкий CPU-контур на этих anchors.",
notProved: "Не доказано, что красный non-ground слой не режет дорогу, траву или допустимые просветы. Нет независимой terrain truth, полного replay, realtime graph integration и модели корпуса.",
decision: "Открыть десять anchors по очереди. Если красное остаётся на реальных препятствиях и не перекрывает видимую опорную поверхность, TGS идёт в полный shadow; иначе кандидат отклоняется без ручной подгонки порогов под эти кадры.",
}}
/>
)}
/>
);
}
@@ -0,0 +1,202 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
RecordedEvidenceSemanticClass,
RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
fetchE47SemanticSlamResult,
type E47SemanticSlamResult,
} from "../../core/laboratory/e47SemanticSlam";
import {
fetchM49TgsFullShadowSpatialChunk,
type M49TgsFullShadowResult,
type M49TgsFullShadowSpatial,
type M49TgsFullShadowSpatialChunk,
type M49TgsFullShadowStateCode,
} from "../../core/laboratory/m49TgsFullShadow";
import {
M4ReplayThreatVisual,
type M4ReplayClassifiedSpatialFrame,
} from "./M4ReplayThreatVisual";
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
{ id: 1, label: "Ground support" },
{ id: 2, label: "Non-ground occupied" },
{ id: 3, label: "Unknown / rejected" },
];
const PALETTE: readonly RecordedEvidenceSemanticPaletteEntry[] = [
{ classId: 1, color: { kind: "token", token: "--nodedc-success-rgb" } },
{ classId: 2, color: { kind: "token", token: "--nodedc-danger-rgb" } },
{ classId: 3, color: { kind: "token", token: "--nodedc-warning-rgb" } },
];
const CHUNK_FRAMES = 24;
const RETAINED_CHUNKS = 4;
function cellState(code: M49TgsFullShadowStateCode): M4ReplayClassifiedSpatialFrame["cellsMapGravityLocal"][number]["state"] {
if (code === 1) return "ground-support";
if (code === 2) return "nonground-occupied";
if (code === 3) return "unknown-rejected";
return "unobserved";
}
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Полный TGS spatial frame недоступен.";
}
export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) {
const [activeSequence, setActiveSequence] = useState<number | null>(null);
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
const [semanticError, setSemanticError] = useState<string | null>(null);
const [chunks, setChunks] = useState<ReadonlyMap<number, M49TgsFullShadowSpatialChunk>>(
() => new Map(),
);
const [error, setError] = useState<string | null>(null);
const inFlightRef = useRef(new Map<number, AbortController>());
const activeChunkStart = activeSequence === null
? null
: Math.floor(activeSequence / CHUNK_FRAMES) * CHUNK_FRAMES;
const activeChunkStartRef = useRef(activeChunkStart);
activeChunkStartRef.current = activeChunkStart;
useEffect(() => {
const controller = new AbortController();
setSemantic(null);
setSemanticError(null);
void fetchE47SemanticSlamResult({
resultId: result.source.linkedSemanticResultId,
signal: controller.signal,
})
.then((next) => {
if (controller.signal.aborted) return;
if (!next || next.baseM4ResultId !== result.source.linkedVisualResultId) {
throw new Error("Semantic archive не совпал с исходным M4 timeline.");
}
setSemantic(next);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setSemanticError(message(caught));
});
return () => controller.abort();
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
useEffect(() => {
for (const controller of inFlightRef.current.values()) controller.abort();
inFlightRef.current.clear();
setChunks(new Map());
setError(null);
return () => {
for (const controller of inFlightRef.current.values()) controller.abort();
inFlightRef.current.clear();
};
}, [result.resultId]);
useEffect(() => {
if (activeChunkStart === null) return;
const desiredStarts = [activeChunkStart, activeChunkStart + CHUNK_FRAMES]
.filter((start) => start < result.timeline.frameCount);
const desired = new Set(desiredStarts);
for (const [start, controller] of inFlightRef.current) {
if (desired.has(start)) continue;
controller.abort();
inFlightRef.current.delete(start);
}
for (const start of desiredStarts) {
if (chunks.has(start) || inFlightRef.current.has(start)) continue;
const controller = new AbortController();
inFlightRef.current.set(start, controller);
void fetchM49TgsFullShadowSpatialChunk(result.resultId, start, CHUNK_FRAMES, {
signal: controller.signal,
})
.then((chunk) => {
if (controller.signal.aborted) return;
setChunks((current) => {
const next = new Map(current);
next.set(start, chunk);
const center = activeChunkStartRef.current ?? start;
const retained = [...next.keys()]
.sort((left, right) => Math.abs(left - center) - Math.abs(right - center))
.slice(0, RETAINED_CHUNKS);
return new Map(retained.map((key) => [key, next.get(key)!]));
});
if (start === activeChunkStartRef.current) setError(null);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted && start === activeChunkStartRef.current) {
setError(message(caught));
}
})
.finally(() => {
if (inFlightRef.current.get(start) === controller) inFlightRef.current.delete(start);
});
break;
}
}, [activeChunkStart, chunks, result.resultId, result.timeline.frameCount]);
const spatial = useMemo<M49TgsFullShadowSpatial | null>(() => {
if (activeSequence === null || activeChunkStart === null) return null;
return chunks.get(activeChunkStart)?.frames.find(
(frame) => frame.sourceSequence === activeSequence,
) ?? null;
}, [activeChunkStart, activeSequence, chunks]);
const loading = activeSequence !== null && !spatial && !error;
const classifiedFrame = useMemo<M4ReplayClassifiedSpatialFrame | null>(() => {
if (!spatial) return null;
return {
sourceSequence: spatial.sourceSequence,
sampleAvailable: spatial.sampleAvailable,
sourcePointCount: spatial.metrics.eligiblePointCount,
pointsMapGravityLocalXyzM: [],
pointClassIds: [],
cellsMapGravityLocal: spatial.costmap.centersXyM.map((center, index) => ({
centerXyM: center,
zBoundsM: spatial.costmap.zBoundsM[index]!,
state: cellState(spatial.costmap.states[index]!),
})),
cellSizeM: spatial.costmap.cellSizeM,
classes: CLASSES,
palette: PALETTE,
};
}, [spatial]);
const handleSequenceChange = useCallback((sequence: number | null) => {
setActiveSequence(sequence);
}, []);
return (
<>
<M4ReplayThreatVisual
resultId={result.source.linkedVisualResultId}
semantic={semantic ? {
resultId: semantic.resultId,
taxonomy: semantic.taxonomy,
} : undefined}
showReviewAnchorBoxes={false}
reviewLabel="4 489 source-paced TGS frames"
evidenceLabel="M49 · full TGS shadow"
initialSpatialMode="3d"
onActiveSequenceChange={handleSequenceChange}
classifiedSpatialLayer={{
label: "TGS full shadow · causal rolling 1 s",
pointLayerLabel: "SOURCE POINTS",
cellLayerLabel: "TGS COSTMAP",
expectedAtSequence: true,
frame: classifiedFrame,
loading,
error,
replacePointCloud: false,
}}
/>
{semanticError ? (
<div className="m4-replay-threat-visual__pane-status" role="alert">
Semantic overlay недоступен: {semanticError}
</div>
) : null}
</>
);
}
@@ -0,0 +1,90 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M49TgsFullShadowResult } from "../../core/laboratory/m49TgsFullShadow";
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
function number(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
export function M49TgsFullShadowResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: M49TgsFullShadowResult;
}) {
const accepted = result.decision.candidateRetained;
const status = accepted
? "Source-paced CPU shadow принят; визуальное и integrated-graph качество ещё проверяются"
: "Source-paced CPU shadow не прошёл performance gate";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.9T5 · полный source-paced TRAVEL TGS shadow"
description="Один CPU-only процесс прошёл весь recorded timeline RAVNOVES00 в исходном темпе. Камера остаётся владельцем времени; dense source cloud сохраняется, поверх него показывается четырёхсостояний TGS costmap."
status={status}
statusTone={accepted ? "success" : "danger"}
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · gravity-aligned LiDAR · causal ${number(result.configuration.historySeconds)} с` },
{ label: "Timeline", value: `${result.timeline.frameCount.toLocaleString("ru-RU")} кадров · ${result.timeline.availableLidarFrameCount.toLocaleString("ru-RU")} LiDAR · ${result.timeline.missingLidarFrameCount} UNOBSERVED` },
{ label: "Нагрузка", value: `Worker 006 CPU-only · GPU 0 · ${number(result.timeline.effectiveFps, 3)} source FPS` },
{ label: "Authority", value: "REPLAY-SIMULATED · navigation/actuation OFF · integrated graph отдельно" },
]}
brief={{
question: "Удерживает ли готовый TRAVEL TGS полный десятигерцовый replay без очереди и потери кадров?",
approach: "Все 4 489 camera frames планируются по исходным timestamps. Для 3 928 доступных LiDAR frames выполняется causal rolling 1 s; 561 пропуск остаётся полностью UNOBSERVED.",
principalResult: `${result.timeline.frameCount}/4 489 frames и ${result.pointAccounting.eligible.toLocaleString("ru-RU")} eligible points учтены; TGS p95/p99 ${number(result.performance.candidateTgsMs.p95, 2)}/${number(result.performance.candidateTgsMs.p99, 2)} мс, completion age p99 ${number(result.performance.completionAgeMs.p99, 2)} мс, capacity drops ${result.performance.capacityDropCount}.`,
limitation: "Это isolated CPU shadow. Он ещё не доказывает качество красных occupied-ячеек, проходимость для конкретного корпуса или регрессию FPS полного world-state graph.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: "travel-tgs-full-source-paced-shadow/v1",
components: [
{ kind: "source", name: "RAVNOVES00", version: "4 489-frame recorded timeline", role: "camera-owned source clock + registered LiDAR", identitySha256: result.source.sourcePackSha256 },
{ kind: "algorithm", name: "TRAVEL GroundSeg", version: "95dc2fbd66a343efd9060c45a5711b6307a950a4", role: "gravity-aligned ground/non-ground separation; AOS OFF", identitySha256: result.configuration.configSha256 },
{ kind: "algorithm", name: "fail-closed costmap adapter", version: "v1", role: "occupied > rejected > ground > unobserved; no free inference", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.9T5 VISUAL EVIDENCE · FULL CAMERA TIMELINE + SOURCE CLOUD + TGS COSTMAP"
title="Полный timeline: dense исходные точки сохранены, TGS-ячейки синхронны каждому кадру"
kind="recorded-replay"
resizable
>
<M49TgsFullShadowEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Что доказал полный прогон"
status={status}
statusTone={accepted ? "success" : "danger"}
metrics={[
{ label: "Timeline", value: `${result.timeline.frameCount}/4 489`, hint: `${result.timeline.availableLidarFrameCount} LiDAR + ${result.timeline.missingLidarFrameCount} explicit UNOBSERVED` },
{ label: "TGS p95 / p99", value: `${number(result.performance.candidateTgsMs.p95, 2)} / ${number(result.performance.candidateTgsMs.p99, 2)} мс`, hint: "чистый candidate stage на CPU" },
{ label: "Completion age p99", value: `${number(result.performance.completionAgeMs.p99, 2)} мс`, hint: "от source timestamp до готового frame result" },
{ label: "Capacity drops", value: String(result.performance.capacityDropCount), hint: "кадры не отбрасывались ради темпа" },
{ label: "Point accounting", value: "100%", hint: `${result.pointAccounting.eligible.toLocaleString("ru-RU")} eligible points` },
]}
conclusion={{
proved: "Полный CPU-only TGS shadow воспроизводимо проходит recorded source clock, сохраняет fail-closed представление и не использует AOS/GPU.",
notProved: "Не приняты visual traversability, модель корпуса, камера-проекция TGS и нагрузка после встраивания в полный realtime world-state graph.",
decision: accepted
? "Кандидат остаётся. Просмотреть полный timeline, затем подключить shadow к realtime graph и измерить общий FPS/latency regression."
: "Кандидат не встраивать; сначала локализовать performance gate, который не прошёл полный replay.",
}}
/>
)}
/>
);
}
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import {
Button,
Icon,
@@ -12,6 +12,7 @@ import {
import { ObservationTimeline } from "../../components/ObservationTimeline";
import {
LaboratoryMetricEvidenceScene,
type LaboratoryMetricCellEvidence,
type LaboratoryMetricEvidenceSceneHandle,
type LaboratoryMetricSceneMode,
} from "../../components/laboratory/LaboratoryMetricEvidenceScene";
@@ -103,6 +104,34 @@ export interface M4ReplayThreatReviewAnchor {
sourceSequence: number;
extentXyxyNormalized: readonly [number, number, number, number];
matchedAtThreshold: boolean;
statusLabel?: string;
}
export interface M4ReplayClassifiedSpatialFrame {
sourceSequence: number;
sampleAvailable?: boolean;
sourcePointCount?: number;
pointsMapGravityLocalXyzM: readonly (readonly [number, number, number])[];
pointClassIds: readonly (number | null)[];
cellsMapGravityLocal: readonly {
centerXyM: readonly [number, number];
zBoundsM: readonly [number | null, number | null];
state: LaboratoryMetricCellEvidence["state"];
}[];
cellSizeM: number;
classes: readonly RecordedEvidenceSemanticClass[];
palette: readonly RecordedEvidenceSemanticPaletteEntry[];
}
export interface M4ReplayClassifiedSpatialLayer {
label: string;
pointLayerLabel: string;
cellLayerLabel: string;
expectedAtSequence: boolean;
frame: M4ReplayClassifiedSpatialFrame | null;
loading: boolean;
error: string | null;
replacePointCloud?: boolean;
}
const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
@@ -115,6 +144,9 @@ export function M4ReplayThreatVisual({
reviewLabel = "Контрольные примеры M4.8R1",
timelineEndpointRoot,
evidenceLabel = "M4.6",
initialSpatialMode = null,
classifiedSpatialLayer,
onActiveSequenceChange,
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
@@ -123,9 +155,14 @@ export function M4ReplayThreatVisual({
reviewLabel?: string;
timelineEndpointRoot?: string;
evidenceLabel?: string;
initialSpatialMode?: LaboratoryMetricSceneMode | null;
classifiedSpatialLayer?: M4ReplayClassifiedSpatialLayer;
onActiveSequenceChange?: (sequence: number | null) => void;
}) {
const [mediaMode, setMediaMode] = useState<M4ThreatMediaMode | null>("video");
const [spatialMode, setSpatialMode] = useState<LaboratoryMetricSceneMode | null>(null);
const [spatialMode, setSpatialMode] = useState<LaboratoryMetricSceneMode | null>(
initialSpatialMode,
);
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showLocalSurface, setShowLocalSurface] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
@@ -220,6 +257,9 @@ export function M4ReplayThreatVisual({
}, [resultId]);
if (timelineFrame.activeFrame) lastFrameRef.current = timelineFrame.activeFrame;
const frame = timelineFrame.activeFrame ?? lastFrameRef.current;
useEffect(() => {
onActiveSequenceChange?.(timelineFrame.activeSequence);
}, [onActiveSequenceChange, timelineFrame.activeSequence]);
const lastSpatialFrameRef = useRef<{
resultId: string;
frame: M4ThreatTimelineFrame;
@@ -303,12 +343,12 @@ export function M4ReplayThreatVisual({
);
}, [frame, metadata.timeline, showStaticObstacles]);
const activeBoxes = useMemo(
() => [
() => classifiedSpatialLayer ? [] : [
...boxes(frame?.cameraProposals ?? []),
...staticObstacleBoxes,
...reviewAnchorBoxes,
],
[frame, reviewAnchorBoxes, staticObstacleBoxes],
[classifiedSpatialLayer, frame, reviewAnchorBoxes, staticObstacleBoxes],
);
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
() => semantic?.taxonomy.map((item) => ({
@@ -363,6 +403,81 @@ export function M4ReplayThreatVisual({
return status === 2 || status === 3 ? classId : null;
});
}, [semantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
const activeSpatialFrame = spatialFrame?.sequence === timelineFrame.activeSequence
? spatialFrame
: null;
const classifiedSpatialFrame = classifiedSpatialLayer?.frame?.sourceSequence === timelineFrame.activeSequence
? classifiedSpatialLayer?.frame ?? null
: null;
const lastClassifiedSpatialFrameRef = useRef<{
resultId: string;
frame: M4ReplayClassifiedSpatialFrame;
} | null>(null);
if (classifiedSpatialLayer?.frame) {
lastClassifiedSpatialFrameRef.current = { resultId, frame: classifiedSpatialLayer.frame };
}
const displayedClassifiedSpatialFrame = classifiedSpatialFrame
?? (lastClassifiedSpatialFrameRef.current?.resultId === resultId
? lastClassifiedSpatialFrameRef.current.frame
: null);
const replaceClassifiedPointCloud = classifiedSpatialLayer?.replacePointCloud ?? true;
const nominalSensorHeightM = metadata.timeline?.rig.nominalSensorHeightM ?? 0;
const mapGravityLocalSensorToBodyGround = useCallback((
point: readonly [number, number, number],
): readonly [number, number, number] => {
const basis = activeSpatialFrame?.bodyFrame?.basisMapFromBody;
const rotated: readonly [number, number, number] = basis ? [
basis[0][0] * point[0] + basis[1][0] * point[1] + basis[2][0] * point[2],
basis[0][1] * point[0] + basis[1][1] * point[1] + basis[2][1] * point[2],
basis[0][2] * point[0] + basis[1][2] * point[1] + basis[2][2] * point[2],
] : point;
// TGS evidence is translation-only map-gravity-local with the current LiDAR
// as its origin. The metric scene uses the body ground projection as z=0.
return [rotated[0], rotated[1], rotated[2] + nominalSensorHeightM];
}, [activeSpatialFrame?.bodyFrame?.basisMapFromBody, nominalSensorHeightM]);
const classifiedPointsBody = useMemo(
() => displayedClassifiedSpatialFrame?.pointsMapGravityLocalXyzM.map(
mapGravityLocalSensorToBodyGround,
) ?? [],
[displayedClassifiedSpatialFrame, mapGravityLocalSensorToBodyGround],
);
const classifiedCellsBody = useMemo<readonly LaboratoryMetricCellEvidence[]>(
() => displayedClassifiedSpatialFrame?.cellsMapGravityLocal.map((cell) => {
const body = mapGravityLocalSensorToBodyGround([
cell.centerXyM[0],
cell.centerXyM[1],
0,
]);
const [minimumSensorRelativeZ, maximumSensorRelativeZ] = cell.zBoundsM;
return {
centerBodyXyM: [body[0], body[1]],
zBoundsM: [
minimumSensorRelativeZ === null
? null
: minimumSensorRelativeZ + nominalSensorHeightM,
maximumSensorRelativeZ === null
? null
: maximumSensorRelativeZ + nominalSensorHeightM,
],
state: cell.state,
};
}) ?? [],
[displayedClassifiedSpatialFrame, mapGravityLocalSensorToBodyGround, nominalSensorHeightM],
);
const classifiedCellCounts = useMemo(() => ({
ground: classifiedSpatialFrame?.cellsMapGravityLocal.filter(
(cell) => cell.state === "ground-support",
).length ?? 0,
occupied: classifiedSpatialFrame?.cellsMapGravityLocal.filter(
(cell) => cell.state === "nonground-occupied",
).length ?? 0,
rejected: classifiedSpatialFrame?.cellsMapGravityLocal.filter(
(cell) => cell.state === "unknown-rejected",
).length ?? 0,
unobserved: classifiedSpatialFrame?.cellsMapGravityLocal.filter(
(cell) => cell.state === "unobserved",
).length ?? 0,
}), [classifiedSpatialFrame]);
const sceneObstacles = useMemo(() => spatialFrame?.metricObstacles.map((obstacle) => ({
id: obstacle.componentId,
decision: obstacle.assessment.decision,
@@ -522,7 +637,53 @@ export function M4ReplayThreatVisual({
</div>
) : null;
const spatialLayerControls = (
const spatialLayerControls = classifiedSpatialLayer ? (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label={`Слои ${classifiedSpatialLayer.label}`}
>
<Button
size="compact"
shape="pill"
variant={showCurrentIncrement ? "primary" : "secondary"}
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
{classifiedSpatialLayer.pointLayerLabel}
</Button>
<Button
size="compact"
shape="pill"
variant={showLocalSurface ? "primary" : "secondary"}
aria-pressed={showLocalSurface}
title="Bounded local SLAM surface · visual-derived"
onClick={() => setShowLocalSurface((visible) => !visible)}
>
LOCAL SLAM
</Button>
<Button
size="compact"
shape="pill"
variant={showRollingMap ? "primary" : "secondary"}
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
{classifiedSpatialLayer.cellLayerLabel}
</Button>
{semantic ? (
<Button
size="compact"
shape="pill"
variant={showSpatialSemantic ? "primary" : "secondary"}
aria-pressed={showSpatialSemantic}
onClick={() => setShowSpatialSemantic((visible) => !visible)}
>
SEMANTICS
</Button>
) : null}
</div>
) : (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
@@ -626,7 +787,7 @@ export function M4ReplayThreatVisual({
value={String(selectedReviewAnchorIndex)}
options={reviewAnchors.map((anchor, index) => ({
value: String(index),
label: `${index + 1}/${reviewAnchors.length} · кадр ${anchor.sourceSequence + 1} · ${anchor.matchedAtThreshold ? "покрыт" : "пропуск"}`,
label: `${index + 1}/${reviewAnchors.length} · кадр ${anchor.sourceSequence + 1} · ${anchor.statusLabel ?? (anchor.matchedAtThreshold ? "покрыт" : "пропуск")}`,
}))}
variant="split"
menuWidth="anchor"
@@ -652,7 +813,11 @@ export function M4ReplayThreatVisual({
? splitPrimarySize
: 100;
const overlay = metadata.timeline && frame ? (
const overlaySequence = timelineFrame.activeSequence ?? frame?.sequence ?? null;
const overlaySessionSeconds = overlaySequence === null
? null
: (metadata.timeline?.frameTimesNs[overlaySequence] ?? 0) / 1_000_000_000;
const overlay = metadata.timeline && overlaySequence !== null ? (
<div
className="l3-visual-audit__overlay m4-replay-threat-visual__overlay"
style={{
@@ -661,9 +826,9 @@ export function M4ReplayThreatVisual({
>
<div>
<span>RAVNOVES00 · recorded realtime</span>
<strong>frame {frame.sequence + 1}/{metadata.timeline.frameCount}</strong>
<strong>frame {overlaySequence + 1}/{metadata.timeline.frameCount}</strong>
<small>
+{(frame.sessionSeconds - metadata.timeline.timelineStartSeconds).toFixed(3)} с
+{((overlaySessionSeconds ?? metadata.timeline.timelineStartSeconds) - metadata.timeline.timelineStartSeconds).toFixed(3)} с
· {displayingBufferedFrame
? "держим последний кадр, следующий в буфере"
: playbackController.playback.playing ? "воспроизведение" : "пауза / seek"}
@@ -671,39 +836,56 @@ export function M4ReplayThreatVisual({
</div>
<div>
<span>Spatial evidence</span>
<strong>
{currentIncrementObstacles.length} current · {rollingMapObstacles.length} rolling
{metadata.timeline.occupancyProvenanceDelivery
? ` · ${lowStepObstacles.length} low-step`
: ""}
</strong>
<small>
{spatialFrame
? `${spatialFrame.pointCloudSampleCount}/${spatialFrame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "квалифицированный spatial frame ещё не получен"}
{frame.worldStateAvailable
? " · world-state delivered"
: ` · world-state gap (${frame.terminalOutcome})`}
{accumulatedCameraPoints
? ` · camera points ${accumulatedCameraPoints.sampleCount}/${accumulatedCameraPoints.projectedPointCount} · causal ${accumulatedCameraPoints.windowSeconds.toFixed(1)} с / ${accumulatedCameraPoints.sourceFrameCount} frames`
: pointCloudOverlay
? ` · camera points ${frame.cameraProjectedSampleCount}/${frame.cameraProjectedPointCount} exact-current · накопление загружается`
: showMediaPoints && cameraPointOverlay.error
? " · накопленное camera cloud недоступно"
: ""}
{semantic && spatialSemanticFrame
? ` · semantic L ${spatialSemanticFrame.counts.labeled} · A ${spatialSemanticFrame.counts.ambiguous} · U ${spatialSemanticFrame.counts.unprojected} · Ø ${spatialSemanticFrame.counts.absent}`
: semantic ? " · semantic buffer" : ""}
</small>
<strong>{classifiedSpatialLayer
? classifiedSpatialFrame
? replaceClassifiedPointCloud
? `${classifiedSpatialFrame.pointsMapGravityLocalXyzM.length.toLocaleString("ru-RU")} TGS points · ${classifiedSpatialFrame.cellsMapGravityLocal.length.toLocaleString("ru-RU")} cells`
: `${(activeSpatialFrame?.pointCloudSourceCount ?? classifiedSpatialFrame.sourcePointCount ?? 0).toLocaleString("ru-RU")} source points · ${classifiedSpatialFrame.cellsMapGravityLocal.length.toLocaleString("ru-RU")} TGS cells`
: "TGS spatial buffer"
: `${currentIncrementObstacles.length} current · ${rollingMapObstacles.length} rolling${metadata.timeline.occupancyProvenanceDelivery ? ` · ${lowStepObstacles.length} low-step` : ""}`}</strong>
<small>{classifiedSpatialLayer
? classifiedSpatialFrame
? classifiedSpatialFrame.sampleAvailable === false
? "LiDAR отсутствует · все ячейки принудительно UNOBSERVED · causal rolling 1 s"
: activeSpatialFrame
? "map-gravity-local · all eligible points accounted · causal rolling 1 s"
: "TGS рассчитан · linked source cloud недоступен для этого кадра"
: classifiedSpatialLayer.error ?? `Открываем ${classifiedSpatialLayer.label}`
: (
<>
{spatialFrame
? `${spatialFrame.pointCloudSampleCount}/${spatialFrame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "квалифицированный spatial frame ещё не получен"}
{frame
? frame.worldStateAvailable
? " · world-state delivered"
: ` · world-state gap (${frame.terminalOutcome})`
: " · world-state frame unavailable"}
{accumulatedCameraPoints
? ` · camera points ${accumulatedCameraPoints.sampleCount}/${accumulatedCameraPoints.projectedPointCount} · causal ${accumulatedCameraPoints.windowSeconds.toFixed(1)} с / ${accumulatedCameraPoints.sourceFrameCount} frames`
: pointCloudOverlay && frame
? ` · camera points ${frame.cameraProjectedSampleCount}/${frame.cameraProjectedPointCount} exact-current · накопление загружается`
: showMediaPoints && cameraPointOverlay.error
? " · накопленное camera cloud недоступно"
: ""}
{semantic && spatialSemanticFrame
? ` · semantic L ${spatialSemanticFrame.counts.labeled} · A ${spatialSemanticFrame.counts.ambiguous} · U ${spatialSemanticFrame.counts.unprojected} · Ø ${spatialSemanticFrame.counts.absent}`
: semantic ? " · semantic buffer" : ""}
</>
)}</small>
</div>
<div>
<span>Virtual corridor</span>
<strong>
{spatialFrame?.decisionCounts.threat ?? 0} threat · nearest {nearest === null ? "—" : `${nearest.toFixed(2)} м`}
</strong>
<small>
{metadata.timeline.corridor.forwardLengthM} м · body {metadata.timeline.rig.lengthM}×{metadata.timeline.rig.widthM} м · REPLAY-SIMULATED
</small>
<span>{classifiedSpatialLayer ? "TGS fail-closed" : "Virtual corridor"}</span>
<strong>{classifiedSpatialLayer
? classifiedSpatialFrame
? `${classifiedCellCounts.occupied} occupied · ${classifiedCellCounts.rejected} rejected · ${classifiedCellCounts.unobserved} unobserved`
: classifiedSpatialLayer.loading || displayingBufferedFrame ? "loading" : "unavailable"
: `${spatialFrame?.decisionCounts.threat ?? 0} threat · nearest ${nearest === null ? "—" : `${nearest.toFixed(2)} м`}`}</strong>
<small>{classifiedSpatialLayer
? classifiedSpatialFrame
? `${classifiedCellCounts.ground} ground-support · visual review only · navigation authority OFF`
: "visual review only · navigation authority OFF"
: `${metadata.timeline.corridor.forwardLengthM} м · body ${metadata.timeline.rig.lengthM}×${metadata.timeline.rig.widthM} м · REPLAY-SIMULATED`}</small>
</div>
</div>
) : undefined;
@@ -802,27 +984,59 @@ export function M4ReplayThreatVisual({
</div>
</div>
) : null}
{spatialFrame ? (
{(!classifiedSpatialLayer ? spatialFrame : displayedClassifiedSpatialFrame) ? (
<LaboratoryMetricEvidenceScene
ref={metricSceneRef}
pointCloudBodyXyzM={spatialFrame.pointCloudBodyXyzM}
pointCloudBodyXyzM={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedPointsBody
: activeSpatialFrame?.pointCloudBodyXyzM ?? []}
localSurfaceBodyXyzM={localSurface.pointsBodyXyzM}
obstacles={sceneObstacles}
obstacles={displayedClassifiedSpatialFrame ? [] : sceneObstacles}
rig={timeline.rig}
corridor={timeline.corridor}
occupiedVoxelSizeM={timeline.occupiedVoxelSizeM}
occupiedVoxelSizeM={displayedClassifiedSpatialFrame?.cellSizeM ?? timeline.occupiedVoxelSizeM}
mode={spatialMode}
label={`${evidenceLabel} exact current increment, bounded local SLAM surface and rolling occupancy`}
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
showLowStep={showLowStep}
pointSemanticClassIds={alignedSemanticPointIds}
semanticClasses={semanticClasses}
semanticPalette={semanticPalette}
showLowStep={displayedClassifiedSpatialFrame ? false : showLowStep}
pointSemanticClassIds={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? displayedClassifiedSpatialFrame.pointClassIds
: alignedSemanticPointIds}
semanticClasses={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? displayedClassifiedSpatialFrame.classes
: semanticClasses}
semanticPalette={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? displayedClassifiedSpatialFrame.palette
: semanticPalette}
classifiedCells={classifiedCellsBody}
classifiedCellSizeM={displayedClassifiedSpatialFrame?.cellSizeM}
showClassifiedCells={showRollingMap}
/>
) : null}
{frame && !frame.spatialAvailable ? (
{classifiedSpatialLayer && !displayedClassifiedSpatialFrame ? (
<div className="l3-visual-audit__state" role={classifiedSpatialLayer.error ? "alert" : "status"}>
{classifiedSpatialLayer.loading || displayingBufferedFrame
? <span className="busy-indicator" aria-hidden="true" />
: <Icon name="alert" size={18} />}
<span>{classifiedSpatialLayer.error
?? (classifiedSpatialLayer.loading || displayingBufferedFrame
? `Открываем ${classifiedSpatialLayer.label}`
: classifiedSpatialLayer.expectedAtSequence
? `Открываем ${classifiedSpatialLayer.label}`
: `${classifiedSpatialLayer.label} рассчитан только на 10 контрольных кадров.`)}</span>
</div>
) : null}
{classifiedSpatialFrame?.sampleAvailable === false ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
Кадр {classifiedSpatialFrame.sourceSequence + 1}: LiDAR отсутствует; все 2 244 TGS-ячейки явно UNOBSERVED.
</div>
) : classifiedSpatialFrame && !activeSpatialFrame ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
Кадр {classifiedSpatialFrame.sourceSequence + 1}: TGS costmap показан cell-only; linked source cloud для отрисовки отсутствует.
</div>
) : frame && !frame.spatialAvailable ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
{spatialFrame
? `На кадре ${frame.sequence + 1} нет body frame; держим spatial evidence кадра ${spatialFrame.sequence + 1}.`
@@ -105,6 +105,20 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
experimentName: "RF-DETR native risk review and temporal identity",
variantName: "M4.8Q · native raw KB4 review · quality not adjudicated",
},
"m49-tgs-fail-closed-evidence": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + gravity-aligned LiDAR`,
experimentId: "m49-tgs-fail-closed-evidence",
experimentName: "TRAVEL TGS fail-closed traversability evidence",
variantName: "M4.9T4 · 10 anchors · causal rolling 1 s · AOS OFF",
},
"m49-tgs-full-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + gravity-aligned LiDAR`,
experimentId: "m49-tgs-full-shadow",
experimentName: "TRAVEL TGS complete source-paced shadow",
variantName: "M4.9T5 · 4 489 frames · causal rolling 1 s · CPU-only",
},
"m47-reference-graph-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + LiDAR dual evidence`,
@@ -25,6 +25,8 @@ function mergeResults(
m48r3StaticOccupancy: next.m48r3StaticOccupancy ?? current.m48r3StaticOccupancy,
m48s: next.m48s ?? current.m48s,
m48t: next.m48t ?? current.m48t,
m49Tgs: next.m49Tgs ?? current.m49Tgs,
m49TgsFull: next.m49TgsFull ?? current.m49TgsFull,
m4Threat: next.m4Threat ?? current.m4Threat,
l3: next.l3 ?? current.l3,
l31: next.l31 ?? current.l31,
@@ -124,6 +126,8 @@ export function useAdvancedLaboratoryCatalog({
"m48-small-static-passage-regression",
"m48-static-occupancy-qualification",
"m48r3-static-occupancy-shadow",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
].includes(selectedWorkId)
&& !indexedResultId
) return;
@@ -7,6 +7,7 @@ let server;
let fetchAdvancedLaboratoryResults;
let fetchAdvancedLaboratoryIndex;
let fetchAdvancedLaboratoryResult;
let fetchM49TgsAnchorSpatial;
let AdvancedLaboratoryContractError;
let buildLaboratoryCatalog;
let buildLaboratoryProfiles;
@@ -836,6 +837,77 @@ function e40() {
};
}
function m49View() {
const anchors = [171, 306, 368, 402, 450, 509, 525, 744, 1122, 1856];
return {
schema_version: "missioncore.m49-tgs-fail-closed-view/v1",
result_id: `m49-tgs-fail-closed-${"2".repeat(64)}`,
created_at_utc: "2026-08-26T17:09:43Z",
source: {
source_id: "RAVNOVES00",
source_session_id: "20260720T065719Z_viewer_live",
source_pack_sha256: "3".repeat(64),
linked_visual_result_id: `m4-threat-replay-${"4".repeat(64)}`,
anchor_frame_indices: anchors,
},
configuration: {
profile_id: "m49-ravnoves00-tgs-fail-closed-evidence/v1",
config_sha256: "5".repeat(64),
coordinate_frame: "map-gravity-local",
primary_profile: "causal_rolling_1s",
cell_size_m: 0.45,
radius_m: 12,
},
execution: {
worker: "Worker 006",
device: "cpu",
gpu_used: false,
wrapper_elapsed_seconds: 12.1,
},
metrics: {
anchor_count: 10,
anchor_profile_count: 20,
all_eligible_points_accounted: true,
costmap_cell_count: 2244,
process_wall_current_p50_ms: 20,
process_wall_current_max_ms: 30,
process_wall_rolling_p50_ms: 30,
process_wall_rolling_max_ms: 40,
process_max_rss_kib: 9292,
primary: anchors.map((anchor, slot) => ({
anchor_frame_index: anchor,
slot,
profile_id: "causal_rolling_1s",
point_count: 3,
ground_point_count: 1,
nonground_point_count: 1,
rejected_point_count: 1,
ground_cell_count: 1,
nonground_cell_count: 1,
rejected_cell_count: 1,
unobserved_cell_count: 2241,
all_points_accounted: true,
})),
},
acceptance: {
representation_complete: true,
all_points_accounted: true,
aos_absent: true,
gpu_absent: true,
visual_quality_accepted: false,
traversability_accepted: false,
},
decision: {
state: "visual-review-required",
candidate_retained: true,
next_action: "Review the ten anchors.",
},
limitations: ["bounded diagnostic evidence"],
ground_truth: false,
access: "read-only",
};
}
before(async () => {
server = await createServer({
appType: "custom",
@@ -850,6 +922,9 @@ before(async () => {
fetchAdvancedLaboratoryIndex,
fetchAdvancedLaboratoryResult,
} = await server.ssrLoadModule("/src/core/laboratory/advancedIndex.ts"));
({ fetchM49TgsAnchorSpatial } = await server.ssrLoadModule(
"/src/core/laboratory/m49TgsFailClosed.ts",
));
({
buildLaboratoryCatalog,
buildLaboratoryProfiles,
@@ -1596,6 +1671,62 @@ test("selected advanced LAB fetches only its own strict catalog", async () => {
]);
});
test("selected M49 LAB preserves the sealed fail-closed contract", async () => {
const payload = m49View();
const requests = [];
const decoded = await fetchAdvancedLaboratoryResult(
"m49-tgs-fail-closed-evidence",
{
resultId: payload.result_id,
fetcher: async (input, init) => {
requests.push({ input: String(input), method: init?.method });
return new Response(JSON.stringify(payload), { status: 200 });
},
},
);
assert.equal(decoded.m49Tgs.metrics.anchorCount, 10);
assert.equal(decoded.m49Tgs.metrics.costmapCellCount, 2244);
assert.equal(decoded.m49Tgs.acceptance.visualQualityAccepted, false);
assert.deepEqual(requests, [{
input: `/api/v1/laboratory/m49/tgs-fail-closed/${payload.result_id}`,
method: "GET",
}]);
});
test("M49 anchor fetch keeps every point and all four costmap states", async () => {
const resultId = m49View().result_id;
const centers = Array.from({ length: 2244 }, (_, index) => [index * 0.45, 0]);
const states = Array.from({ length: 2244 }, (_, index) => index % 4);
const zBounds = states.map((state) => state === 0 ? [null, null] : [0, 0.2]);
const decoded = await fetchM49TgsAnchorSpatial(resultId, 171, "causal_rolling_1s", {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.m49-tgs-anchor-spatial/v1",
result_id: resultId,
anchor_frame_index: 171,
source_sequence: 171,
profile: "causal_rolling_1s",
coordinate_frame: "map-gravity-local",
points_xyz_m: [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
point_states: [1, 2, 3],
costmap: {
cell_size_m: 0.45,
radius_m: 12,
centers_xy_m: centers,
states,
z_bounds_m: zBounds,
},
all_points_accounted: true,
aos_used: false,
access: "read-only",
}), { status: 200 }),
});
assert.deepEqual(decoded.pointStates, [1, 2, 3]);
assert.equal(decoded.costmap.states.length, 2244);
assert.deepEqual(new Set(decoded.costmap.states), new Set([0, 1, 2, 3]));
});
test("keeps valid LAB catalogs available when one transport endpoint fails", async () => {
const decoded = await fetchAdvancedLaboratoryResults({
fetcher: async (input) => {
@@ -0,0 +1,95 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let fetchM49TgsFullShadowSpatialChunk;
const resultId = `m49-tgs-full-shadow-${"a".repeat(64)}`;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({ fetchM49TgsFullShadowSpatialChunk } = await server.ssrLoadModule(
"/src/core/laboratory/m49TgsFullShadow.ts",
));
});
after(async () => {
await server?.close();
});
test("M4.9T5 chunk keeps every missing-LiDAR cell explicitly UNOBSERVED", async () => {
let requestedUrl = "";
const centers = Array.from({ length: 2244 }, (_, index) => [index * 0.45, 0]);
const unobserved = Array.from({ length: 2244 }, () => 0);
const zBounds = Array.from({ length: 2244 }, () => [null, null]);
const metrics = {
eligible_point_count: 0,
ground_point_count: 0,
nonground_point_count: 0,
rejected_point_count: 0,
occupied_cell_count: 0,
};
const chunk = await fetchM49TgsFullShadowSpatialChunk(resultId, 7, 1, {
fetcher: async (url) => {
requestedUrl = String(url);
return new Response(JSON.stringify({
schema_version: "missioncore.m49-tgs-full-shadow-spatial-chunk/v1",
result_id: resultId,
start: 7,
count: 1,
coordinate_frame: "map-gravity-local",
costmap: {
cell_size_m: 0.45,
radius_m: 12,
centers_xy_m: centers,
},
frames: [{
source_sequence: 7,
source_frame_index: 7,
session_seconds: 36.119857292,
sample_available: false,
states: unobserved,
z_bounds_m: zBounds,
metrics,
}],
}), { status: 200, headers: { "Content-Type": "application/json" } });
},
});
assert.equal(
requestedUrl,
`/api/v1/laboratory/m49/tgs-full-shadow/${resultId}/spatial/chunk?start=7&count=1`,
);
assert.equal(chunk.count, 1);
assert.equal(chunk.frames[0].sampleAvailable, false);
assert.equal(chunk.frames[0].costmap.states.length, 2244);
assert.deepEqual(new Set(chunk.frames[0].costmap.states), new Set([0]));
});
test("M4.9T5 viewer prefetches 24-frame immutable chunks", async () => {
const [source, contract] = await Promise.all([
readFile(
new URL("../src/workspaces/laboratory/M49TgsFullShadowEvidence.tsx", import.meta.url),
"utf8",
),
readFile(
new URL("../src/core/laboratory/m49TgsFullShadow.ts", import.meta.url),
"utf8",
),
]);
assert.match(source, /const CHUNK_FRAMES = 24/);
assert.match(source, /activeChunkStart \+ CHUNK_FRAMES/);
assert.match(source, /fetchM49TgsFullShadowSpatialChunk/);
assert.match(source, /sampleAvailable: spatial\.sampleAvailable/);
assert.match(source, /fetchE47SemanticSlamResult/);
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
assert.match(source, /semantic=\{semantic \? \{/);
assert.match(contract, /linked_semantic_result_id/);
});
@@ -799,7 +799,27 @@ test("M4.6 viewer keeps media and spatial panes on one playback clock", async ()
assert.match(metricScene, /OrbitControls/);
assert.match(visual, /LOCAL SLAM/);
assert.match(visual, /showLocalSurface/);
assert.match(visual, /pointCloudBodyXyzM=\{spatialFrame\.pointCloudBodyXyzM\}/);
assert.match(
visual,
/pointCloudBodyXyzM=\{displayedClassifiedSpatialFrame && replaceClassifiedPointCloud[\s\S]*\? classifiedPointsBody[\s\S]*: activeSpatialFrame\?\.pointCloudBodyXyzM \?\? \[\]\}/,
);
assert.match(
visual,
/const classifiedSpatialFrame = classifiedSpatialLayer\?\.frame\?\.sourceSequence === timelineFrame\.activeSequence[\s\S]*const displayedClassifiedSpatialFrame = classifiedSpatialFrame[\s\S]*lastClassifiedSpatialFrameRef/,
);
assert.match(visual, /localSurfaceBodyXyzM=\{localSurface\.pointsBodyXyzM\}/);
assert.match(visual, /showLocalSurface=\{showLocalSurface\}/);
assert.match(visual, /\{semantic \? \([\s\S]*>\s*SEMANTICS\s*<\/Button>/);
assert.match(visual, /все 2 244 TGS-ячейки явно UNOBSERVED/);
assert.match(
visual,
/mapGravityLocalSensorToBodyGround[\s\S]*rotated\[2\] \+ nominalSensorHeightM/,
);
assert.match(
visual,
/zBoundsM: \[[\s\S]*minimumSensorRelativeZ \+ nominalSensorHeightM[\s\S]*maximumSensorRelativeZ \+ nominalSensorHeightM/,
);
assert.match(visual, /classifiedCells=\{classifiedCellsBody\}/);
assert.match(metricScene, /Локальная SLAM-поверхность/);
assert.match(visual, /showJumpToEnd=\{false\}/);
assert.doesNotMatch(visual, /Назад на 5 секунд/);
@@ -0,0 +1,10 @@
{
"schema_version": "missioncore.laboratory-evidence-definition/v1",
"work_id": "m49-tgs-fail-closed-evidence",
"evidence": {
"runtime_relative_root": "m49/tgs-fail-closed-results",
"result_id_prefix": "m49-tgs-fail-closed",
"document_name": "manifest.json",
"schema_version": "missioncore.m49-tgs-fail-closed-result/v1"
}
}
@@ -0,0 +1,10 @@
{
"schema_version": "missioncore.laboratory-evidence-definition/v1",
"work_id": "m49-tgs-full-shadow",
"evidence": {
"runtime_relative_root": "m49/tgs-full-shadow-results",
"result_id_prefix": "m49-tgs-full-shadow",
"document_name": "manifest.json",
"schema_version": "missioncore.m49-tgs-full-shadow-lab/v1"
}
}
+28
View File
@@ -162,6 +162,34 @@
"run": "missioncore.laboratory-run/v1",
"evidence": "missioncore.m48t-risk-quality-temporal-lab/v1"
}
},
{
"work_id": "m49-tgs-fail-closed-evidence",
"lifecycle": "experimental",
"isolation": "bounded-adapter",
"adapter_id": "experimental.m49-tgs-fail-closed-evidence/v1",
"input_roles": ["repository_root"],
"contracts": {
"source": "missioncore.m49-tgs-worker-evidence/v1",
"provider": "missioncore.travel-tgs-ground-segmentation/v1",
"graph": "missioncore.m49-fail-closed-costmap-evidence/v1",
"run": "missioncore.laboratory-run/v1",
"evidence": "missioncore.m49-tgs-fail-closed-result/v1"
}
},
{
"work_id": "m49-tgs-full-shadow",
"lifecycle": "experimental",
"isolation": "bounded-adapter",
"adapter_id": "experimental.m49-tgs-full-shadow/v1",
"input_roles": ["repository_root"],
"contracts": {
"source": "missioncore.m49-tgs-full-shadow-result/v1",
"provider": "missioncore.travel-tgs-ground-segmentation/v1",
"graph": "missioncore.m49-tgs-full-source-paced-shadow/v1",
"run": "missioncore.laboratory-run/v1",
"evidence": "missioncore.m49-tgs-full-shadow-lab/v1"
}
}
],
"legacy_work_ids": [
+14
View File
@@ -267,6 +267,20 @@
"signal": "progress",
"lifecycle": "current",
"visual_evidence": "available"
},
{
"catalog_id": "m49-tgs-fail-closed-evidence",
"evidence_id": "m49-tgs-fail-closed-9d5cb089bb5cc23f829acb47eda52eaa886db3b60f8fa7d57e02e27f642e837b",
"signal": "progress",
"lifecycle": "current",
"visual_evidence": "available"
},
{
"catalog_id": "m49-tgs-full-shadow",
"evidence_id": "m49-tgs-full-shadow-0faeaaf3aba8dccae974eab51ff9cccf264a13ec285abb1920c1e5a09a7e87bc",
"signal": "progress",
"lifecycle": "current",
"visual_evidence": "available"
}
]
}
@@ -0,0 +1,77 @@
{
"schema_version": "missioncore.m49-tgs-full-shadow-profile/v1",
"profile_id": "m49-ravnoves00-tgs-full-shadow/v1",
"source": {
"source_id": "RAVNOVES00",
"session_id": "20260720T065719Z_viewer_live",
"source_pack_id": "e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b",
"source_pack_sha256": "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944",
"travel_revision": "95dc2fbd66a343efd9060c45a5711b6307a950a4",
"input_coordinate_frame": "map-gravity-local-translation-only",
"expected_timeline_frames": 4489,
"expected_available_lidar_frames": 3928
},
"tgs": {
"max_range_m": 80.0,
"min_range_m": 1.0,
"resolution_m": 8.0,
"num_iterations": 3,
"num_lowest_representative_points": 5,
"minimum_points": 10,
"seed_threshold_m": 0.5,
"distance_threshold_m": 0.125,
"outlier_threshold_m": 0.3,
"normal_threshold": 0.94,
"weight_threshold": 200.0,
"lcc_normal_similarity": 0.03,
"lcc_planar_distance_m": 0.1,
"obstacle_height_m": 1.0,
"refine_mode": true
},
"profile": {
"id": "causal_rolling_1s",
"history_seconds": 1.0,
"local_radius_m": 12.0,
"missing_lidar_policy": "all-cells-unobserved"
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": 0.45,
"radius_m": 12.0,
"state_priority": [
"NONGROUND_OCCUPIED",
"UNKNOWN_REJECTED",
"GROUND_SUPPORT",
"UNOBSERVED"
]
},
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3
},
"acceptance": {
"recorded_source_rate_hz": 10.0,
"minimum_effective_timeline_fps": 10.0,
"candidate_stage_p95_ms_max": 25.0,
"candidate_stage_p99_ms_max": 50.0,
"completion_age_p99_ms_max": 100.0,
"capacity_drop_count_max": 0,
"unaccounted_available_frame_count_max": 0,
"unaccounted_eligible_point_count_max": 0
},
"invariants": {
"all_eligible_input_points_accounted": true,
"aos_allowed": false,
"lidar_orientation_applied_to_tgs_input": false,
"map_gravity_axis_preserved": true,
"missing_support_means_free": false,
"missing_lidar_means_unobserved": true,
"unobserved_cells_are_emitted": true,
"future_frames_used": false,
"camera_projection_is_authoritative": false,
"gpu_allowed": false,
"navigation_or_actuation_allowed": false
}
}
@@ -326,7 +326,23 @@ invocation. It emits a deterministic `0.45 m`, `12 m` local evidence grid while
preserving unobserved cells. See
[`experiments/perception/M49_TGS_FAIL_CLOSED_EVIDENCE_2026-08-26.md`](../experiments/perception/M49_TGS_FAIL_CLOSED_EVIDENCE_2026-08-26.md).
Representation/accounting is accepted for the visual gate; obstacle and
traversability quality are not yet accepted.
traversability quality are not yet accepted. The sealed result is now published
in the canonical LAB as
`m49-tgs-fail-closed-9d5cb089bb5cc23f829acb47eda52eaa886db3b60f8fa7d57e02e27f642e837b`,
linked to the current M4 v3 recorded timeline. Publication adds no navigation
or actuation authority.
The subsequent complete source-paced TGS-only shadow passed on `2026-08-26`.
It accounted for all `4,489` timeline frames and all `63,646,163` eligible
points, emitted the `561` missing-LiDAR frames as explicit all-cell
`UNOBSERVED`, sustained `10.003945 FPS`, measured TGS `p95/p99` at
`1.694/2.08973 ms`, completion-age `p99` at `41.42845548 ms`, and recorded zero
capacity drops. The immutable result and canonical LAB acceptance are recorded
in
[`experiments/perception/M49_TGS_FULL_SHADOW_ACCEPTANCE_2026-08-26.md`](../experiments/perception/M49_TGS_FULL_SHADOW_ACCEPTANCE_2026-08-26.md).
This accepts the CPU source-paced shadow and retains the candidate. It does not
accept visual traversability quality, integrated graph performance, navigation
or actuation.
### T4 — Candidate C occupancy/ESDF probe
@@ -345,12 +361,15 @@ traversability quality are not yet accepted.
## Immediate next action
Import the sealed gravity-aligned TGS evidence pack into one laboratory review
surface and review the ten anchors in metric 3D/costmap space. Preserve
Review the published full gravity-aligned TGS timeline in camera, metric 3D and
costmap space. Preserve
`GROUND_SUPPORT`, `NONGROUND_OCCUPIED`, `UNKNOWN_REJECTED` and `UNOBSERVED` as
separate products; do not use AOS or infer free cells from absent
republication. Add diagnostic camera projection only after the metric evidence
is accepted. Candidate A T2 replay remains blocked by the failed unmodified T1
gate. No LOW-STEP tuning, new object model, camera resize, fisheye
republication. Attach the unchanged candidate to the realtime world-state graph
in non-authoritative shadow mode and measure complete-graph FPS, latency,
resource use and queue drops. If occupied evidence carpets the route, soft
traversable vegetation or usable gaps, reject TGS without tuning it against the
review evidence. Candidate A T2 replay remains blocked by the failed unmodified
T1 gate. No LOW-STEP tuning, new object model, camera resize, fisheye
rectification, manual dataset or parallel heavy Worker job is authorized by
this decision.
@@ -1,7 +1,8 @@
# M4.9 gravity-aligned TGS fail-closed evidence — 2026-08-26
Status: **evidence adapter completed**; visual obstacle/traversability quality,
full-source realtime, navigation and actuation remain disabled
Status: **evidence adapter and canonical LAB publication completed**; visual
obstacle/traversability quality, full-source realtime, navigation and actuation
remain disabled
## Decision
@@ -13,7 +14,9 @@ accounts for every point inside TRAVEL's declared `1–80 m` processing range.
This closes the representation and accounting gate. It does not accept visual
quality. Several anchors still contain a high non-ground share, so the next
gate is a 3D/costmap review of the sealed output rather than threshold tuning or
camera boxes.
camera boxes. The metric output is now available in the canonical LAB on port
`8000` as an operator-review surface; publication does not upgrade it to an
accepted terrain provider.
## Frozen identity
@@ -125,15 +128,73 @@ Worker root:
| `inputs/input-manifest.json` | `13,357` | `7a22e61606e1fa9ab61d47417f941b590a68b29e4529d4c290992ce7eb89a642` |
| `tgs-timing.tsv` | `638` | `a8f439996205286335ac392b37f51b54056d04e9cb07e03648c52c1e8f2f6ae8` |
## Canonical LAB publication
- LAB result:
`m49-tgs-fail-closed-9d5cb089bb5cc23f829acb47eda52eaa886db3b60f8fa7d57e02e27f642e837b`;
- linked current visual timeline:
`m4-threat-replay-2a953c5f27f2a5b1dddc5c658c1de2c323d7796084a099c024987a1da03aa324`;
- source LiDAR pack SHA-256 remains
`0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944`;
- the earlier immutable seal beginning `200b57cc` is superseded only because it
linked the retired M4 v2 visual profile, whose timeline is incompatible with
the current v3 API. No Worker evidence byte or TGS result was changed.
The LAB reuses the canonical recorded-realtime camera and spatial instrument.
It exposes the ten anchors, the full classified TGS point set, the four-state
costmap, `VIDEO/CAMERA`, `3D/PLAN`, independent point/costmap visibility and
exact per-anchor counts. Camera proposal boxes, AOS clusters, future frames and
navigation authority are absent. Browser acceptance confirmed that every
operator control is independent and that a loading anchor cannot temporarily
display unrelated M4 obstacle statistics.
The published TGS coordinates are intentionally sensor-origin
`map-gravity-local`: the ground is therefore approximately `-1.25 m` below the
current LiDAR. The canonical LAB presentation converts these immutable values
to the scene's body-ground origin by adding the rig's sealed
`nominal_sensor_height_m = 1.25` after the gravity-to-body rotation. The same
offset is applied to point heights and costmap z-bounds; x/y evidence and the
sealed Worker result remain unchanged. A regression check across all ten
anchors puts the median displayed ground points in `-0.299…+0.135 m` and the
median ground-cell centres in `-0.308…+0.237 m`. On anchor `1856`, where the
presentation defect was reported, those medians are `+0.004 m` and `-0.063 m`.
The residual variation is measured terrain/pose variation, not the previous
systematic `1.25 m` lift.
## Preliminary corridor triage
The published evidence was also projected into the frozen virtual body
corridor (`-0.5…8.0 m` longitudinal, `±0.5 m` lateral) without changing the
sealed result. The table counts costmap cell centres inside that corridor:
| Anchor | Current occupied | Rolling occupied | Rolling unobserved | Preliminary reading |
| ---: | ---: | ---: | ---: | --- |
| `171` | `0` | `8` | `6` | unresolved rolling band at `5.63…7.90 m` |
| `306` | `0` | `0` | `12` | no corridor obstruction |
| `368` | `1` | `1` | `16` | compact occupied support at `6.34 m` |
| `402` | `0` | `1` | `10` | compact occupied support at `1.89 m` |
| `450` | `0` | `0` | `8` | no corridor obstruction |
| `509` | `0` | `0` | `7` | no corridor obstruction |
| `525` | `0` | `0` | `9` | no corridor obstruction |
| `744` | `0` | `3` | `25` | weakly observed; requires explicit review |
| `1122` | `0` | `2` | `24` | rolling recovers compact obstacle support |
| `1856` | `1` | `1` | `13` | compact occupied support at `4.14 m` |
This is a diagnostic, not ground truth. It shows why the causal window cannot
simply be removed: anchor `1122` is entirely unobserved in the current-only
corridor but gains ground and occupied evidence in rolling. It also identifies
the opposing risk: at anchor `171`, rolling adds eight occupied cells where the
camera/PLAN comparison appears to show an open route apart from side geometry.
Anchor `744` remains too weakly observed for acceptance. These two anchors keep
the visual gate open even though the remaining frames do not show a continuous
occupied carpet.
## Next gate
Import the sealed `evidence.npz` into one laboratory review surface with:
1. 3D points colored by the four evidence states;
2. top-down `0.45 m` costmap cells in the same local coordinate frame;
3. anchor selection and exact counts from `result.json`;
4. no AOS clusters, camera boxes, future frames or physical authority.
The operator review must focus on the mandatory obstacle/gap cases and on the
large non-ground populations listed above. Camera projection follows only if
the metric evidence is accepted.
Review the published ten anchors in both metric `3D` and `PLAN`. The operator
must focus on the mandatory obstacle/gap cases and the large non-ground
populations listed above. Accept TGS for a full recorded-source shadow only if
red non-ground evidence remains on real geometry without carpeting the visible
route, soft traversable vegetation or usable gaps. Otherwise reject this
candidate without tuning thresholds against these ten anchors. Camera
projection follows only if the metric evidence is accepted.
@@ -0,0 +1,105 @@
# M4.9T5 full source-paced TRAVEL TGS shadow — 2026-08-26
Status: **recorded source-paced CPU shadow accepted; candidate retained**.
Visual traversability quality, full-graph performance, navigation and actuation
remain unaccepted.
## Decision
The gravity-aligned TGS-only candidate completed the entire `RAVNOVES00`
recorded timeline on Worker 006. Every source timeline frame and every eligible
LiDAR point is represented. No frame was dropped for capacity, AOS was never
invoked, no GPU was requested and the canonical Triton container remained
healthy with the same identity.
This accepts TGS as a bounded CPU shadow candidate. It does not yet make TGS a
navigation authority or prove that its occupied/ground interpretation is
correct for vegetation, terrain, gaps or the future vehicle envelope.
## Frozen identity
| Item | Identity |
| --- | --- |
| LAB result | `m49-tgs-full-shadow-ef98de7db7596d48e8c8c0549ce68e6704ee03e87c8c4bcf1e3e748b7ccb032e` |
| Worker run | `Worker 006 / ravnoves00-full-001` |
| Mission Core revision used by Worker | `40c850b167dda366d8aa45d828520168affaf9fd` |
| Deterministic Worker artifact | `5e0ea16c7a5cc760463836718b0cd8b0006ffc4b202e5f706a20a86ef2f912ab` |
| Source pack SHA-256 | `0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944` |
| TGS config SHA-256 | `c2e07010aaee78259d36c057962d6bfb885349251ff7356d867e5813e632881c` |
| Linked visual result | `m4-threat-replay-2a953c5f27f2a5b1dddc5c658c1de2c323d7796084a099c024987a1da03aa324` |
| Linked semantic result | `e47-semantic-slam-f8c53ba12e9719856195890c7dcd2ba6c114434674159f948064368012e6b3bc` |
The profile is a `0.45 m`, `12 m` radius, `1 s` causal rolling
`map-gravity-local` costmap. The four states remain separate:
`GROUND_SUPPORT`, `NONGROUND_OCCUPIED`, `UNKNOWN_REJECTED` and `UNOBSERVED`.
## Full-run result
| Measurement | Result |
| --- | ---: |
| Timeline | `4,489 / 4,489` frames |
| LiDAR available / missing | `3,928 / 561` |
| Recorded duration / effective rate | `448.623 s / 10.003945 FPS` |
| Eligible points | `63,646,163` |
| Ground / non-ground / rejected | `16,579,467 / 47,046,815 / 19,881` |
| Unaccounted points | `0` |
| TGS p50 / p95 / p99 / max | `1.190 / 1.694 / 2.08973 / 9.106 ms` |
| Completion age p50 / p95 / p99 / max | `17.920792 / 29.140775 / 41.428455 / 659.980295 ms` |
| Capacity drops | `0` |
| Worker wrapper wall time | `569.401084 s` |
All formal source-paced, point-accounting and capacity gates passed. The
completion-age maximum is retained as an outlier; the accepted gate is the
measured `p99 = 41.43 ms`, not the maximum.
## Fail-closed behavior
The `561` timeline frames without a LiDAR sample are not removed, interpolated
or copied from the preceding frame. Each is emitted as a complete `2,244`-cell
costmap with all states explicitly `UNOBSERVED`, zero occupied cells and zero
source points. This preserves chronology without inventing free space.
## Canonical LAB publication and UI acceptance
The immutable result is published on the canonical Mission Core service at
port `8000`. The LAB reuses the recorded camera timeline and exposes independent
`SOURCE POINTS`, `LOCAL SLAM`, `TGS COSTMAP`, `SEMANTICS`, `3D` and `PLAN`
controls. The semantic mask is the exact E47 full-route archive bound to the
same M4 camera result; it remains diagnostic and does not change TGS states.
The first implementation fetched one large JSON frame at a time. Browser QA
showed that this was only intermittently exact at `1×`. The published viewer
therefore uses immutable chunks of `24` spatial frames and prefetches the next
chunk. A final `1×` browser acceptance sampled twenty consecutive positions
across chunk boundaries: all twenty displayed the exact TGS frame and none
showed the loading placeholder. A real missing-LiDAR frame was separately
accepted with `0 source points`, `2,244 unobserved`, `0 occupied` and no retained
previous cloud.
A follow-up playback acceptance fixed a UI lifecycle defect that remounted the
Three.js scene while the next TGS frame crossed the React buffer boundary. The
scene now retains the last sealed classified frame until the exact next frame
arrives, so orbit controls and the operator-selected view survive continuous
playback. Twelve consecutive `250 ms` playback samples kept one mounted 3D
canvas, a visible bounded `LOCAL SLAM` surface and the synchronized E47 video
mask. Pausing is no longer required to rotate, pan or zoom the 3D view.
## Sealed evidence
| File | Bytes | SHA-256 |
| --- | ---: | --- |
| `costmap-states.npy` | `10,073,444` | `4173f8b6b743755d6d6cbd863c433fe44853974bdd4898002e08574a65783a1a` |
| `costmap-z-bounds-m.npy` | `80,586,656` | `f8d68b5ca09860ef857a22e15e114bbeabde32492d5c821ee59dfa62ae40373a` |
| `frames.ndjson` | `1,178,262` | `878e20806d55cdc1fa16692397e11a0e56af760a83c173c8fc73319a39b0e779` |
| `worker-summary.json` | `1,076` | `487d4be6f3d724bca647aa2e48ba89e7ffd0c78f1bfc5e7e5a01e1db82379dc7` |
## Next gate
Attach this unchanged TGS stage to the realtime world-state graph in shadow
mode alongside the frozen camera semantic-risk provider. Measure complete-graph
FPS, end-to-end latency, CPU/GPU/VRAM and queue drops against the accepted
baseline. Keep navigation authority off. In parallel, review the full camera +
source cloud + TGS timeline for false occupied carpets, missed compact
obstacles, vegetation and usable gaps. Only the combination of acceptable
visual behavior and acceptable integrated-graph regression can promote the
candidate beyond shadow.
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""Publish the verified Worker TGS pack as immutable Mission Core LAB evidence."""
from __future__ import annotations
import argparse
from pathlib import Path
from k1link.laboratory.m49_tgs_fail_closed import (
M49TgsFailClosedError,
seal_m49_tgs_fail_closed,
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=Path, required=True)
parser.add_argument("--destination-root", type=Path, required=True)
parser.add_argument("--profile", type=Path, required=True)
parser.add_argument("--linked-visual-result-id", required=True)
arguments = parser.parse_args()
try:
result = seal_m49_tgs_fail_closed(
source_root=arguments.source_root,
destination_root=arguments.destination_root,
profile_path=arguments.profile,
linked_visual_result_id=arguments.linked_visual_result_id,
)
except (M49TgsFailClosedError, OSError, ValueError) as exc:
parser.error(str(exc))
print(result.result_id)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,187 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseRoot,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
[string]$RunId,
[string]$SourcePackPath = "D:\NDC_MISSIONCORE\runtime\derived\e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b\lidar-pack.npz",
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m49-tgs-full-shadow"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826"
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) {
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
$null = New-Item -ItemType Directory -Path $Path
}
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
-not $item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: directory" }
return $item.FullName
}
function Resolve-DFile([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
$item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: file" }
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return ($Path -replace "\\", "/") }
function Get-Container([string]$Name) {
$rows = @(((& docker inspect $Name) | ConvertFrom-Json))
Assert-LastExitCode "Docker inspection for $Name"
if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" }
return $rows[0]
}
function Assert-Image([string]$Tag, [string]$ExpectedId) {
$rows = @(((& docker image inspect $Tag) | ConvertFrom-Json))
Assert-LastExitCode "Docker image inspection for $Tag"
if ($rows.Count -ne 1 -or [string]$rows[0].Id -cne $ExpectedId) {
throw "Pinned image identity changed for $Tag"
}
}
function Remove-ExactContainer([string]$Name) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$Name$") {
& docker rm --force $Name *> $null
}
}
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") { throw "M49 TGS full shadow is pinned to Worker 006" }
$release = Resolve-DDirectory $ReleaseRoot "M49 TGS full-shadow release" $false
$payload = Resolve-DDirectory (Join-Path $release "payload") "M49 TGS full-shadow payload" $false
$sourcePack = Resolve-DFile $SourcePackPath "RAVNOVES00 source pack"
$output = Resolve-DDirectory $OutputRoot "M49 TGS full-shadow output root" $true
$runCandidate = Join-Path $output $RunId
if (Test-Path -LiteralPath $runCandidate) { throw "M49 TGS full-shadow output already exists" }
$null = New-Item -ItemType Directory -Path $runCandidate
$runOutput = Resolve-DDirectory $runCandidate "M49 TGS full-shadow run output" $false
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
if (
$releaseDocument.schema_version -cne "missioncore.m49-tgs-full-shadow-worker-release/v1" -or
$releaseDocument.worker_id -cne "worker-006" -or
$releaseDocument.candidate_id -cne "travel-tgs-full-shadow"
) { throw "M49 TGS full-shadow release contract changed" }
foreach ($property in $releaseDocument.files.PSObject.Properties) {
$path = Join-Path $payload $property.Name
$actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant()
if ($actual -cne [string]$property.Value.sha256) {
throw "M49 TGS full-shadow payload digest changed: $($property.Name)"
}
}
$sourcePackSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $sourcePack).Hash.ToLowerInvariant()
if ($sourcePackSha -cne "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944") {
throw "RAVNOVES00 source pack digest changed"
}
$os = Get-CimInstance Win32_OperatingSystem
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
if ($freeMemoryGiB -lt 24.0) {
throw ("M49 TGS full shadow requires 24 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB)
}
$tritonBefore = Get-Container "ndc-mission-core-triton"
if (-not $tritonBefore.State.Running -or $tritonBefore.State.Health.Status -cne "healthy") {
throw "Canonical Mission Core Triton must remain healthy during M49 TGS full shadow"
}
Assert-Image $TravelImageTag $TravelImageId
Assert-Image $ParityImageTag $ParityImageId
$prepareName = "ndc-mission-core-m49-tgs-full-prepare-$RunId"
$runName = "ndc-mission-core-m49-tgs-full-run-$RunId"
$analyzeName = "ndc-mission-core-m49-tgs-full-analyze-$RunId"
foreach ($name in @($prepareName, $runName, $analyzeName)) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
throw "M49 TGS full-shadow container name already exists: $name"
}
}
$started = [DateTimeOffset]::UtcNow
try {
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ((Convert-ToDockerPath $sourcePack) + ":/source/lidar-pack.npz:ro") `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$ParityImageTag /release/prepare_tgs_full_shadow_inputs.py `
--source-pack /source/lidar-pack.npz `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /tgs/inputs
Assert-LastExitCode "M49 TGS full-shadow input preparation"
& docker run --rm --name $runName --network none --cpus 16 --memory 24g `
--entrypoint /bin/bash `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$TravelImageTag /release/run_tgs_full_shadow.sh
Assert-LastExitCode "M49 source-paced TGS full-shadow run"
& docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$ParityImageTag /release/build_tgs_full_shadow_evidence.py `
--run-root /tgs `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /tgs/evidence
Assert-LastExitCode "M49 TGS full-shadow evidence analysis"
} finally {
foreach ($name in @($prepareName, $runName, $analyzeName)) { Remove-ExactContainer $name }
}
$completed = [DateTimeOffset]::UtcNow
$resultPath = Join-Path $runOutput "evidence\result.json"
if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) { throw "M49 TGS full-shadow result is missing" }
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
if (
$result.timeline.frame_count -ne 4489 -or
$result.timeline.available_lidar_frame_count -ne 3928 -or
$result.timeline.missing_lidar_frame_count -ne 561 -or
$result.point_accounting.unaccounted -ne 0
) { throw "M49 TGS full-shadow structural acceptance failed" }
$tritonAfter = Get-Container "ndc-mission-core-triton"
if (
-not $tritonAfter.State.Running -or
$tritonAfter.State.Health.Status -cne "healthy" -or
[string]$tritonAfter.Id -cne [string]$tritonBefore.Id
) { throw "Canonical Mission Core Triton changed during M49 TGS full shadow" }
$summary = [ordered]@{
schema_version = "missioncore.m49-tgs-full-shadow-worker-summary/v1"
worker_id = "worker-006"
run_id = $RunId
code_revision = [string]$releaseDocument.code_revision
source_pack_sha256 = $sourcePackSha
travel_image_id = $TravelImageId
parity_image_id = $ParityImageId
started_utc = $started.ToString("o")
wall_seconds = [math]::Round(($completed - $started).TotalSeconds, 6)
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
canonical_triton_id = [string]$tritonAfter.Id
canonical_triton_health = [string]$tritonAfter.State.Health.Status
result_status = [string]$result.status
all_timeline_frames_accounted = $true
all_eligible_points_accounted = $true
aos_used = $false
gpu_requested = $false
integrated_graph_performance_accepted = $false
navigation_or_actuation_allowed = $false
}
$summary | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $runOutput "worker-summary.json") -Encoding utf8
$summary | ConvertTo-Json -Depth 3
@@ -0,0 +1,45 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseRoot,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
[string]$RunId
)
$ErrorActionPreference = "Stop"
$taskName = "MissionCore-M49TgsFullShadow"
$release = (Resolve-Path -LiteralPath $ReleaseRoot).Path
$runner = Join-Path $release "payload\Invoke-M49TgsFullShadow.ps1"
if (-not (Test-Path -LiteralPath $runner -PathType Leaf)) { throw "M49 TGS full-shadow runner is missing" }
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existing -and $existing.State -eq "Running") { throw "$taskName is already running" }
$powerShell = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$arguments = @(
"-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
"-File", "`"$runner`"", "-ReleaseRoot", "`"$release`"", "-RunId", "`"$RunId`""
) -join " "
$userId = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$action = New-ScheduledTaskAction -Execute $powerShell -Argument $arguments -WorkingDirectory $release
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date).AddMinutes(30))
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-ExecutionTimeLimit ([TimeSpan]::FromHours(2))
Register-ScheduledTask `
-TaskName $taskName `
-Action $action `
-Principal $principal `
-Trigger $trigger `
-Settings $settings `
-Description "One-shot CPU-only source-paced TGS full shadow." `
-Force | Out-Null
Start-ScheduledTask -TaskName $taskName
[pscustomobject]@{
task_name = $taskName
run_id = $RunId
release_root = $release
state = (Get-ScheduledTask -TaskName $taskName).State.ToString()
} | ConvertTo-Json -Compress
@@ -0,0 +1,332 @@
#!/usr/bin/env python3
"""Build mmap-friendly evidence for the complete source-paced TGS shadow."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import math
from pathlib import Path
import numpy as np
from build_tgs_fail_closed_evidence import costmap_grid
class FullShadowError(RuntimeError):
"""The complete TGS shadow or its fail-closed contract is invalid."""
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def load_xyzi(path: Path) -> np.ndarray:
if path.is_symlink() or not path.is_file():
raise FullShadowError(f"sealed input is unavailable: {path.name}")
values = np.fromfile(path, dtype=np.float32)
if values.size % 4:
raise FullShadowError(f"sealed XYZI shape changed: {path.name}")
result = values.reshape(-1, 4)
if not np.isfinite(result).all():
raise FullShadowError(f"sealed XYZI is non-finite: {path.name}")
return result
def classify_exact_input(
native: np.ndarray, ground: np.ndarray, nonground: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
ranges = np.linalg.norm(native[:, :2].astype(np.float64), axis=1)
points = np.ascontiguousarray(native[(ranges > 1.0) & (ranges < 80.0), :3])
output = np.ascontiguousarray(np.concatenate((ground[:, :3], nonground[:, :3]), axis=0))
output_states = np.concatenate(
(np.ones(ground.shape[0], dtype=np.uint8), np.full(nonground.shape[0], 2, dtype=np.uint8))
)
key_dtype = np.dtype((np.void, 12))
input_keys = points.view(key_dtype).reshape(-1)
output_keys = output.view(key_dtype).reshape(-1)
input_order = np.argsort(input_keys, kind="stable")
output_order = np.argsort(output_keys, kind="stable")
sorted_input = input_keys[input_order]
sorted_output = output_keys[output_order]
positions = np.searchsorted(sorted_input, sorted_output, side="left")
if sorted_output.size:
group_starts = np.r_[0, np.flatnonzero(sorted_output[1:] != sorted_output[:-1]) + 1]
group_lengths = np.diff(np.r_[group_starts, sorted_output.size])
occurrence = np.arange(sorted_output.size) - np.repeat(group_starts, group_lengths)
targets = positions + occurrence
if (
np.any(targets >= sorted_input.size)
or np.any(sorted_input[targets] != sorted_output)
or np.unique(targets).size != targets.size
):
raise FullShadowError("TGS output is not a multiset subset of its exact input")
else:
targets = np.empty(0, dtype=np.int64)
sorted_states = np.full(points.shape[0], 3, dtype=np.uint8)
sorted_states[targets] = output_states[output_order]
states = np.empty_like(sorted_states)
states[input_order] = sorted_states
return points, states
def rasterize(
points: np.ndarray,
states: np.ndarray,
grid: np.ndarray,
cell_size_m: float,
) -> tuple[np.ndarray, np.ndarray]:
minimum_ix = int(np.min(grid[:, 0]))
maximum_ix = int(np.max(grid[:, 0]))
minimum_iy = int(np.min(grid[:, 1]))
maximum_iy = int(np.max(grid[:, 1]))
lookup = np.full(
(maximum_ix - minimum_ix + 1, maximum_iy - minimum_iy + 1), -1, dtype=np.int32
)
lookup[
grid[:, 0].astype(np.int32) - minimum_ix,
grid[:, 1].astype(np.int32) - minimum_iy,
] = np.arange(grid.shape[0], dtype=np.int32)
cell_xy = np.floor(points[:, :2] / cell_size_m).astype(np.int32)
inside = (
(cell_xy[:, 0] >= minimum_ix)
& (cell_xy[:, 0] <= maximum_ix)
& (cell_xy[:, 1] >= minimum_iy)
& (cell_xy[:, 1] <= maximum_iy)
)
point_indices = np.flatnonzero(inside)
cell_indices = lookup[
cell_xy[inside, 0] - minimum_ix, cell_xy[inside, 1] - minimum_iy
]
valid = cell_indices >= 0
point_indices = point_indices[valid]
cell_indices = cell_indices[valid]
cell_states = np.zeros(grid.shape[0], dtype=np.uint8)
selected_states = states[point_indices]
ground_cells = np.zeros(grid.shape[0], dtype=np.uint8)
rejected_cells = np.zeros(grid.shape[0], dtype=np.uint8)
nonground_cells = np.zeros(grid.shape[0], dtype=np.uint8)
np.maximum.at(ground_cells, cell_indices, (selected_states == 1).astype(np.uint8))
np.maximum.at(rejected_cells, cell_indices, (selected_states == 3).astype(np.uint8))
np.maximum.at(nonground_cells, cell_indices, (selected_states == 2).astype(np.uint8))
cell_states[ground_cells > 0] = 1
cell_states[rejected_cells > 0] = 3
cell_states[nonground_cells > 0] = 2
minimum_z = np.full(grid.shape[0], np.inf, dtype=np.float32)
maximum_z = np.full(grid.shape[0], -np.inf, dtype=np.float32)
np.minimum.at(minimum_z, cell_indices, points[point_indices, 2])
np.maximum.at(maximum_z, cell_indices, points[point_indices, 2])
z_bounds = np.column_stack((minimum_z, maximum_z)).astype(np.float32, copy=False)
z_bounds[~np.isfinite(z_bounds)] = np.nan
return cell_states, z_bounds
def percentile(values: np.ndarray, value: float) -> float:
return float(np.percentile(values.astype(np.float64), value)) if values.size else 0.0
def build(run_root: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise FullShadowError("full-shadow evidence output already exists")
config = json.loads(config_path.read_text(encoding="utf-8"))
if (
config.get("schema_version") != "missioncore.m49-tgs-full-shadow-profile/v1"
or config.get("invariants", {}).get("aos_allowed") is not False
or config.get("invariants", {}).get("gpu_allowed") is not False
or config.get("invariants", {}).get("missing_lidar_means_unobserved") is not True
):
raise FullShadowError("full-shadow profile changed")
manifest_path = run_root / "inputs" / "input-manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
records = manifest.get("records", [])
if (
manifest.get("schema_version") != "missioncore.m49-tgs-full-shadow-input/v1"
or manifest.get("source_pack_sha256") != config["source"]["source_pack_sha256"]
or manifest.get("config_sha256") != sha256_file(config_path)
or manifest.get("future_frames_used") is not False
or len(records) != 4489
or sum(bool(row["sample_available"]) for row in records) != 3928
):
raise FullShadowError("full-shadow input manifest changed")
with (run_root / "tgs-full-timing.tsv").open("r", encoding="utf-8", newline="") as stream:
timing_rows = list(csv.DictReader(stream, delimiter="\t"))
if len(timing_rows) != 4489:
raise FullShadowError("full-shadow timing frame accounting changed")
cell_size = float(config["costmap"]["cell_size_m"])
radius = float(config["costmap"]["radius_m"])
grid = costmap_grid(radius, cell_size)
output_root.mkdir(parents=True)
np.save(output_root / "costmap-cell-indices-xy.npy", grid[:, :2].astype(np.int32))
np.save(output_root / "costmap-cell-centers-xy-m.npy", grid[:, 2:].astype(np.float32))
states_out = np.lib.format.open_memmap(
output_root / "costmap-states.npy", mode="w+", dtype=np.uint8, shape=(4489, grid.shape[0])
)
z_out = np.lib.format.open_memmap(
output_root / "costmap-z-bounds-m.npy",
mode="w+",
dtype=np.float32,
shape=(4489, grid.shape[0], 2),
)
z_out[:] = np.nan
summaries: list[dict[str, object]] = []
eligible_total = ground_total = nonground_total = rejected_total = 0
available_seen = 0
for frame_index, (record, timing) in enumerate(zip(records, timing_rows, strict=True)):
if int(timing["timeline_frame_index"]) != frame_index:
raise FullShadowError("full-shadow timing order changed")
available = bool(record["sample_available"])
if not available:
if int(timing["sample_available"]) != 0:
raise FullShadowError("missing LiDAR frame was processed")
states_out[frame_index] = 0
summaries.append(
{
"timeline_frame_index": frame_index,
"source_frame_index": int(record["source_frame_index"]),
"session_seconds": float(record["session_seconds"]),
"sample_available": False,
"eligible_point_count": 0,
"ground_point_count": 0,
"nonground_point_count": 0,
"rejected_point_count": 0,
"occupied_cell_count": 0,
}
)
continue
available_seen += 1
native_path = run_root / "inputs" / str(record["relative_path"])
if sha256_file(native_path) != record["sha256"]:
raise FullShadowError("sealed gravity-aligned full-shadow input changed")
output = run_root / "outputs" / "causal_rolling_1s"
ground = load_xyzi(output / f"{frame_index}_ground.bin")
nonground = load_xyzi(output / f"{frame_index}_nonground.bin")
points, point_states = classify_exact_input(load_xyzi(native_path), ground, nonground)
cell_states, z_bounds = rasterize(points, point_states, grid, cell_size)
states_out[frame_index] = cell_states
z_out[frame_index] = z_bounds
ground_count = int(np.count_nonzero(point_states == 1))
nonground_count = int(np.count_nonzero(point_states == 2))
rejected_count = int(np.count_nonzero(point_states == 3))
eligible_total += int(points.shape[0])
ground_total += ground_count
nonground_total += nonground_count
rejected_total += rejected_count
summaries.append(
{
"timeline_frame_index": frame_index,
"source_frame_index": int(record["source_frame_index"]),
"session_seconds": float(record["session_seconds"]),
"sample_available": True,
"eligible_point_count": int(points.shape[0]),
"ground_point_count": ground_count,
"nonground_point_count": nonground_count,
"rejected_point_count": rejected_count,
"occupied_cell_count": int(np.count_nonzero(cell_states == 2)),
}
)
states_out.flush()
z_out.flush()
if available_seen != 3928 or eligible_total != ground_total + nonground_total + rejected_total:
raise FullShadowError("full-shadow eligible point accounting failed")
frames_path = output_root / "frames.ndjson"
frames_path.write_text(
"".join(json.dumps(row, sort_keys=True) + "\n" for row in summaries), encoding="utf-8"
)
available_timings = [row for row in timing_rows if int(row["sample_available"]) == 1]
tgs_ms = np.asarray([float(row["tgs_ms"]) for row in available_timings])
completion_ms = np.asarray([float(row["completion_age_ms"]) for row in timing_rows])
capacity_drops = sum(int(row["capacity_drop"]) for row in timing_rows)
duration = float(records[-1]["session_seconds"]) - float(records[0]["session_seconds"])
effective_fps = (len(records) - 1) / duration
thresholds = config["acceptance"]
acceptance = {
"minimum_effective_timeline_fps": effective_fps >= float(thresholds["minimum_effective_timeline_fps"]),
"candidate_stage_p95_ms": percentile(tgs_ms, 95) <= float(thresholds["candidate_stage_p95_ms_max"]),
"candidate_stage_p99_ms": percentile(tgs_ms, 99) <= float(thresholds["candidate_stage_p99_ms_max"]),
"completion_age_p99_ms": percentile(completion_ms, 99) <= float(thresholds["completion_age_p99_ms_max"]),
"capacity_drop_count": capacity_drops <= int(thresholds["capacity_drop_count_max"]),
"all_frames_accounted": len(summaries) == 4489 and available_seen == 3928,
"all_eligible_points_accounted": eligible_total == ground_total + nonground_total + rejected_total,
}
files = {}
for path in sorted(output_root.iterdir()):
if path.is_file() and path.name != "result.json":
files[path.name] = {"bytes": path.stat().st_size, "sha256": sha256_file(path)}
result = {
"schema_version": "missioncore.m49-tgs-full-shadow-result/v1",
"status": "passed" if all(acceptance.values()) else "failed",
"config_sha256": sha256_file(config_path),
"source_pack_sha256": manifest["source_pack_sha256"],
"input_manifest_sha256": sha256_file(manifest_path),
"timeline": {
"frame_count": 4489,
"available_lidar_frame_count": 3928,
"missing_lidar_frame_count": 561,
"duration_seconds": duration,
"effective_fps": effective_fps,
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": cell_size,
"radius_m": radius,
"cell_count": int(grid.shape[0]),
},
"point_accounting": {
"eligible": eligible_total,
"ground": ground_total,
"nonground": nonground_total,
"rejected": rejected_total,
"unaccounted": eligible_total - ground_total - nonground_total - rejected_total,
},
"performance": {
"candidate_tgs_ms": {
"p50": percentile(tgs_ms, 50),
"p95": percentile(tgs_ms, 95),
"p99": percentile(tgs_ms, 99),
"max": float(np.max(tgs_ms)),
},
"completion_age_ms": {
"p50": percentile(completion_ms, 50),
"p95": percentile(completion_ms, 95),
"p99": percentile(completion_ms, 99),
"max": float(np.max(completion_ms)),
},
"capacity_drop_count": capacity_drops,
},
"acceptance": acceptance,
"files": files,
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"realtime_accepted": bool(all(acceptance.values())),
"integrated_graph_performance_accepted": False,
"navigation_or_actuation_allowed": False,
},
}
(output_root / "result.json").write_text(
json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
return result
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--run-root", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
arguments = parser.parse_args()
result = build(arguments.run_root, arguments.config, arguments.output_root)
print(json.dumps({"status": result["status"], **result["performance"]}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Prepare every available RAVNOVES00 frame for the source-paced TGS shadow."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import numpy as np
from prepare_tgs_fail_closed_inputs import (
EXPECTED_ARRAYS,
SOURCE_PACK_SHA256,
TgsInputError,
_frame_points,
_validate_source,
gravity_local_xyzi,
sha256_file,
)
FULL_SCHEMA = "missioncore.m49-tgs-full-shadow-profile/v1"
INPUT_SCHEMA = "missioncore.m49-tgs-full-shadow-input/v1"
TIMELINE_FRAME_COUNT = 4_489
AVAILABLE_LIDAR_FRAME_COUNT = 3_928
def _bytes_sha256(content: bytes) -> str:
return hashlib.sha256(content).hexdigest()
def prepare(source_pack: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise TgsInputError("TGS full-shadow input output root already exists")
if sha256_file(source_pack) != SOURCE_PACK_SHA256:
raise TgsInputError("RAVNOVES00 lidar-pack digest changed")
config = json.loads(config_path.read_text(encoding="utf-8"))
source = config.get("source", {})
profile = config.get("profile", {})
invariants = config.get("invariants", {})
if (
config.get("schema_version") != FULL_SCHEMA
or source.get("source_pack_sha256") != SOURCE_PACK_SHA256
or source.get("expected_timeline_frames") != TIMELINE_FRAME_COUNT
or source.get("expected_available_lidar_frames") != AVAILABLE_LIDAR_FRAME_COUNT
or source.get("input_coordinate_frame")
!= "map-gravity-local-translation-only"
or profile.get("id") != "causal_rolling_1s"
or profile.get("missing_lidar_policy") != "all-cells-unobserved"
or invariants.get("lidar_orientation_applied_to_tgs_input") is not False
or invariants.get("future_frames_used") is not False
or invariants.get("missing_lidar_means_unobserved") is not True
):
raise TgsInputError("TGS full-shadow profile changed")
required = EXPECTED_ARRAYS | {"source_frame_indices", "pose_quaternions_map_from_lidar"}
with np.load(source_pack, allow_pickle=False) as archive:
if not required.issubset(archive.files):
raise TgsInputError("RAVNOVES00 lidar-pack members changed")
arrays = {name: archive[name] for name in required}
_validate_source(arrays)
if (
arrays["source_frame_indices"].shape != (TIMELINE_FRAME_COUNT,)
or arrays["source_frame_indices"].dtype != np.int64
or arrays["pose_quaternions_map_from_lidar"].shape != (TIMELINE_FRAME_COUNT, 4)
or arrays["pose_quaternions_map_from_lidar"].dtype != np.float64
or int(np.count_nonzero(arrays["sample_available"])) != AVAILABLE_LIDAR_FRAME_COUNT
):
raise TgsInputError("RAVNOVES00 full timeline contract changed")
sequence_root = output_root / "profiles" / "causal_rolling_1s" / "velodyne"
sequence_root.mkdir(parents=True)
seconds = arrays["session_seconds"]
availability = arrays["sample_available"]
positions = arrays["pose_positions_map"]
history_seconds = float(profile["history_seconds"])
local_radius_m = float(profile["local_radius_m"])
records: list[dict[str, object]] = []
schedule_rows = [
"timeline_frame_index\tsource_frame_index\tsession_seconds\tavailable_slot\tpoint_count"
]
available_slot = 0
for frame_index in range(TIMELINE_FRAME_COUNT):
base = {
"timeline_frame_index": frame_index,
"frame_index": int(arrays["frame_indices"][frame_index]),
"source_frame_index": int(arrays["source_frame_indices"][frame_index]),
"session_seconds": float(seconds[frame_index]),
"position_map_m": [float(value) for value in positions[frame_index]],
"sample_available": bool(availability[frame_index]),
}
if not bool(availability[frame_index]):
records.append(
{
**base,
"available_slot": None,
"point_count": 0,
"contributing_frame_indices": [],
"relative_path": None,
"bytes": 0,
"sha256": None,
}
)
schedule_rows.append(
f"{frame_index}\t{base['source_frame_index']}\t{seconds[frame_index]:.9f}\t-1\t0"
)
continue
start = int(np.searchsorted(seconds, seconds[frame_index] - history_seconds, side="left"))
contributors = tuple(
index for index in range(start, frame_index + 1) if bool(availability[index])
)
if not contributors or contributors[-1] != frame_index:
raise TgsInputError("causal full-shadow profile does not contain its current frame")
points_map = np.concatenate(
[_frame_points(arrays, index) for index in contributors], axis=0
)
relative_xy = points_map[:, :2].astype(np.float64) - positions[frame_index, :2]
points_map = points_map[np.linalg.norm(relative_xy, axis=1) <= local_radius_m]
native = gravity_local_xyzi(points_map, positions[frame_index])
if native.shape[0] == 0:
raise TgsInputError("available full-shadow frame produced an empty cloud")
content = np.ascontiguousarray(native).tobytes()
target = sequence_root / f"{available_slot:06d}.bin"
target.write_bytes(content)
records.append(
{
**base,
"available_slot": available_slot,
"point_count": int(native.shape[0]),
"contributing_frame_indices": list(contributors),
"relative_path": target.relative_to(output_root).as_posix(),
"bytes": len(content),
"sha256": _bytes_sha256(content),
}
)
schedule_rows.append(
f"{frame_index}\t{base['source_frame_index']}\t{seconds[frame_index]:.9f}"
f"\t{available_slot}\t{native.shape[0]}"
)
available_slot += 1
if available_slot != AVAILABLE_LIDAR_FRAME_COUNT or len(records) != TIMELINE_FRAME_COUNT:
raise TgsInputError("TGS full-shadow frame accounting changed")
schedule_path = output_root / "schedule.tsv"
schedule_path.write_text("\n".join(schedule_rows) + "\n", encoding="utf-8")
manifest = {
"schema_version": INPUT_SCHEMA,
"source_pack_sha256": SOURCE_PACK_SHA256,
"config_sha256": sha256_file(config_path),
"coordinate_frame": "map-gravity-local",
"transform": "translation-only-preserve-map-gravity-axis",
"intensity_policy": "zero-filled-algorithm-compatibility-only",
"future_frames_used": False,
"timeline_frame_count": TIMELINE_FRAME_COUNT,
"available_lidar_frame_count": AVAILABLE_LIDAR_FRAME_COUNT,
"missing_lidar_frame_count": TIMELINE_FRAME_COUNT - AVAILABLE_LIDAR_FRAME_COUNT,
"schedule": {
"path": "schedule.tsv",
"bytes": schedule_path.stat().st_size,
"sha256": sha256_file(schedule_path),
},
"records": records,
}
manifest_path = output_root / "input-manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
return manifest
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-pack", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
arguments = parser.parse_args()
manifest = prepare(arguments.source_pack, arguments.config, arguments.output_root)
print(
json.dumps(
{
"ok": True,
"timeline_frames": manifest["timeline_frame_count"],
"available_lidar_frames": manifest["available_lidar_frame_count"],
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,181 @@
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include "travel/kitti_loader.hpp"
#include "travel/point_types.hpp"
#include "travel/tgs.hpp"
namespace {
using Clock = std::chrono::steady_clock;
struct ScheduleRow {
std::size_t timeline_frame_index;
long long source_frame_index;
double session_seconds;
long long available_slot;
std::size_t point_count;
};
std::vector<ScheduleRow> readSchedule(const std::string& path) {
std::ifstream input(path);
if (!input) {
throw std::runtime_error("cannot open full-shadow schedule");
}
std::string line;
std::getline(input, line);
if (line != "timeline_frame_index\tsource_frame_index\tsession_seconds\tavailable_slot\tpoint_count") {
throw std::runtime_error("full-shadow schedule header changed");
}
std::vector<ScheduleRow> rows;
while (std::getline(input, line)) {
if (line.empty()) {
continue;
}
std::istringstream stream(line);
ScheduleRow row{};
if (!(stream >> row.timeline_frame_index >> row.source_frame_index >> row.session_seconds
>> row.available_slot >> row.point_count)) {
throw std::runtime_error("invalid full-shadow schedule row");
}
if (row.timeline_frame_index != rows.size()) {
throw std::runtime_error("full-shadow schedule is not contiguous");
}
rows.push_back(row);
}
if (rows.size() != 4489) {
throw std::runtime_error("full-shadow timeline frame count changed");
}
return rows;
}
void writeXYZI(const std::string& path, const travel::PointCloud<PointXYZILID>& cloud) {
std::ofstream output(path, std::ios::binary);
if (!output) {
throw std::runtime_error("cannot open full-shadow TGS output");
}
for (const auto& point : cloud.points) {
const float row[4] = {point.x, point.y, point.z, point.intensity};
output.write(reinterpret_cast<const char*>(row), sizeof(row));
}
if (!output) {
throw std::runtime_error("cannot write full-shadow TGS output");
}
}
double milliseconds(Clock::duration duration) {
return std::chrono::duration<double, std::milli>(duration).count();
}
} // namespace
int main(int argc, char** argv) {
if (argc != 5) {
std::cerr << "Usage: run_tgs_full_shadow <sequence_dir> <schedule.tsv> <output_dir> <timing.tsv>\n";
return 1;
}
try {
const std::string sequence_dir = argv[1];
const std::string schedule_path = argv[2];
const std::string output_dir = argv[3];
const std::string timing_path = argv[4];
const auto schedule = readSchedule(schedule_path);
KittiLoader loader(sequence_dir);
if (loader.size() != 3928) {
throw std::runtime_error("full-shadow available LiDAR frame count changed");
}
std::filesystem::create_directories(output_dir);
std::ofstream timing(timing_path);
if (!timing) {
throw std::runtime_error("cannot open full-shadow timing output");
}
timing << "timeline_frame_index\tsource_frame_index\tsession_seconds\tsample_available"
<< "\tavailable_slot\tinput_points\tground_points\tnonground_points"
<< "\ttgs_ms\tstage_wall_ms\tqueue_delay_ms\tcompletion_age_ms\tcapacity_drop\n";
timing << std::fixed << std::setprecision(6);
const double first_source_seconds = schedule.front().session_seconds;
const auto run_started = Clock::now();
std::size_t expected_slot = 0;
for (const auto& row : schedule) {
const auto target = run_started + std::chrono::duration_cast<Clock::duration>(
std::chrono::duration<double>(row.session_seconds - first_source_seconds));
const auto before_wait = Clock::now();
if (before_wait < target) {
std::this_thread::sleep_until(target);
}
const auto stage_started = Clock::now();
const double queue_delay_ms = std::max(0.0, milliseconds(stage_started - target));
std::size_t input_points = 0;
std::size_t ground_points = 0;
std::size_t nonground_points = 0;
double tgs_seconds = 0.0;
if (row.available_slot >= 0) {
if (static_cast<std::size_t>(row.available_slot) != expected_slot) {
throw std::runtime_error("full-shadow available slot order changed");
}
auto input_xyzi = loader.cloud(expected_slot);
if (!input_xyzi || input_xyzi->size() != row.point_count) {
throw std::runtime_error("full-shadow input point count changed");
}
auto input = std::make_shared<travel::PointCloud<PointXYZILID>>();
input->reserve(input_xyzi->size());
for (const auto& point : input_xyzi->points) {
PointXYZILID value{};
value.x = point.x;
value.y = point.y;
value.z = point.z;
value.intensity = point.intensity;
value.label = 0;
value.id = 0;
input->emplace_back(value);
}
travel::TravelGroundSeg<PointXYZILID> tgs;
tgs.setParams(
80.0, 1.0, 8.0, 3, 5, 10, 0.5, 0.125, 0.3, 0.940,
200.0, 0.03, 0.1, 1.0, true, false);
travel::PointCloud<PointXYZILID> ground;
travel::PointCloud<PointXYZILID> nonground;
tgs.estimateGround(*input, ground, nonground, tgs_seconds);
input_points = input->size();
ground_points = ground.size();
nonground_points = nonground.size();
const std::string base = output_dir + "/" + std::to_string(row.timeline_frame_index);
writeXYZI(base + "_ground.bin", ground);
writeXYZI(base + "_nonground.bin", nonground);
++expected_slot;
}
const auto completed = Clock::now();
timing << row.timeline_frame_index << '\t' << row.source_frame_index << '\t'
<< row.session_seconds << '\t' << (row.available_slot >= 0 ? 1 : 0) << '\t'
<< row.available_slot << '\t' << input_points << '\t' << ground_points << '\t'
<< nonground_points << '\t' << (tgs_seconds * 1000.0) << '\t'
<< milliseconds(completed - stage_started) << '\t' << queue_delay_ms << '\t'
<< std::max(0.0, milliseconds(completed - target)) << "\t0\n";
if ((row.timeline_frame_index + 1) % 100 == 0) {
timing.flush();
std::cout << "[TGS-FULL] frame=" << (row.timeline_frame_index + 1)
<< "/4489 available=" << expected_slot << "/3928\n";
}
}
timing.flush();
if (expected_slot != 3928) {
throw std::runtime_error("full-shadow available frame accounting changed");
}
std::cout << "[TGS-FULL] complete timeline=4489 available=3928\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "[TGS-FULL] " << error.what() << '\n';
return 2;
}
}
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
readonly INPUT_ROOT=/tgs/inputs
readonly OUTPUT_ROOT=/tgs/outputs/causal_rolling_1s
readonly TIMING_PATH=/tgs/tgs-full-timing.tsv
readonly BINARY=/tmp/run_tgs_full_shadow
test -f "${INPUT_ROOT}/input-manifest.json"
test -f "${INPUT_ROOT}/schedule.tsv"
test ! -e /tgs/outputs
test ! -e "${TIMING_PATH}"
g++ -std=c++17 -O3 -DNDEBUG -pthread \
-I/opt/travel/src/TRAVEL/cpp/travel/core \
-I/usr/include/eigen3 \
/release/run_tgs_full_shadow.cpp \
-o "${BINARY}"
mkdir -p "${OUTPUT_ROOT}"
exec /usr/bin/time -v "${BINARY}" \
"${INPUT_ROOT}/profiles/causal_rolling_1s" \
"${INPUT_ROOT}/schedule.tsv" \
"${OUTPUT_ROOT}" \
"${TIMING_PATH}"
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Build the deterministic Worker 006 release for the complete TGS shadow."""
from __future__ import annotations
import argparse
import gzip
import hashlib
import io
import json
import re
import subprocess
import tarfile
import tempfile
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
SOURCES = (
Path("experiments/perception/worker/m49_t3_travel/prepare_tgs_fail_closed_inputs.py"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_fail_closed_evidence.py"),
Path("experiments/perception/worker/m49_t3_travel/prepare_tgs_full_shadow_inputs.py"),
Path("experiments/perception/worker/m49_t3_travel/run_tgs_full_shadow.cpp"),
Path("experiments/perception/worker/m49_t3_travel/run_tgs_full_shadow.sh"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_full_shadow_evidence.py"),
Path("experiments/perception/worker/Invoke-M49TgsFullShadow.ps1"),
Path("experiments/perception/worker/Invoke-M49TgsFullShadowAsInteractiveUser.ps1"),
Path("config/perception/m49-tgs-full-shadow-v1.json"),
)
PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
class ArtifactBuildError(RuntimeError):
"""The full-shadow release cannot be built from the declared source."""
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def git_revision() -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=REPOSITORY_ROOT, check=True, capture_output=True, text=True
)
return result.stdout.strip()
def tar_info(path: Path, arcname: str) -> tarfile.TarInfo:
info = tarfile.TarInfo(arcname)
info.uid = info.gid = 0
info.uname = info.gname = "root"
info.mtime = 0
if path.is_dir():
info.type = tarfile.DIRTYPE
info.mode = 0o755
else:
info.type = tarfile.REGTYPE
info.mode = 0o755 if path.suffix in {".sh", ".ps1", ".py"} else 0o644
info.size = path.stat().st_size
return info
def write_archive(stage: Path, target: Path) -> None:
members = [stage / "manifest.env", stage / "files.txt", stage / "payload"]
members.extend(sorted((stage / "payload").rglob("*")))
target.parent.mkdir(parents=True, exist_ok=True)
with (
target.open("wb") as raw,
gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed,
tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive,
):
for path in members:
info = tar_info(path, path.relative_to(stage).as_posix())
if path.is_file():
with path.open("rb") as stream:
archive.addfile(info, stream)
else:
archive.addfile(info, io.BytesIO())
def build(patch_id: str, output_directory: Path, *, revision: str | None = None) -> dict[str, object]:
if PATCH_ID.fullmatch(patch_id) is None:
raise ArtifactBuildError("patch id is invalid")
sources = tuple(REPOSITORY_ROOT / source for source in SOURCES)
if any(path.is_symlink() or not path.is_file() for path in sources):
raise ArtifactBuildError("release input is not a regular file")
selected_revision = revision or git_revision()
if re.fullmatch(r"[a-f0-9]{40}", selected_revision) is None:
raise ArtifactBuildError("artifact revision is invalid")
with tempfile.TemporaryDirectory(prefix="mission-core-m49-tgs-full-") as directory:
stage = Path(directory)
payload = stage / "payload"
payload.mkdir()
files: dict[str, dict[str, object]] = {}
for source in sources:
destination = payload / source.name
destination.write_bytes(source.read_bytes())
files[destination.name] = {"bytes": destination.stat().st_size, "sha256": sha256_file(destination)}
release = {
"schema_version": "missioncore.m49-tgs-full-shadow-worker-release/v1",
"patch_id": patch_id,
"code_revision": selected_revision,
"worker_id": "worker-006",
"candidate_id": "travel-tgs-full-shadow",
"license": "GPL-3.0-or-later",
"source_pack_sha256": "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944",
"images": {
"travel": "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f",
"parity": "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0",
},
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"realtime_accepted": False,
"integrated_graph_performance_accepted": False,
"navigation_or_actuation_allowed": False,
},
"files": files,
}
release_path = payload / "release.json"
release_path.write_text(json.dumps(release, indent=2, sort_keys=True) + "\n", encoding="utf-8")
payload_names = sorted((*files, release_path.name))
(stage / "manifest.env").write_text(
f"id={patch_id}\ncomponent=mission-core-worker\ntype=qualification-release\n", encoding="utf-8"
)
(stage / "files.txt").write_text("\n".join(payload_names) + "\n", encoding="utf-8")
target = output_directory.resolve() / f"nodedc-{patch_id}.tgz"
write_archive(stage, target)
return {
"ok": True,
"artifact": str(target),
"sha256": sha256_file(target),
"patch_id": patch_id,
"code_revision": selected_revision,
"payload_files": payload_names,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("patch_id")
parser.add_argument("--output-directory", type=Path, default=REPOSITORY_ROOT / ".runtime/worker-artifacts")
arguments = parser.parse_args()
try:
result = build(arguments.patch_id, arguments.output_directory)
except (ArtifactBuildError, OSError, subprocess.SubprocessError) as exc:
parser.error(str(exc))
print(json.dumps(result, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,440 @@
"""Seal and verify bounded gravity-aligned TRAVEL TGS evidence."""
from __future__ import annotations
import hashlib
import json
import os
import shutil
import tempfile
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Final
M49_TGS_RESULT_SCHEMA: Final = "missioncore.m49-tgs-fail-closed-result/v1"
M49_TGS_REPORT_SCHEMA: Final = "missioncore.m49-tgs-fail-closed-report/v1"
M49_TGS_WORKER_RESULT_SCHEMA: Final = "missioncore.m49-tgs-fail-closed-evidence-result/v1"
M49_TGS_PREFIX: Final = "m49-tgs-fail-closed-"
M49_TGS_PROFILE_ID: Final = "m49-ravnoves00-tgs-fail-closed-evidence/v1"
M49_TGS_ANCHORS: Final = (171, 306, 368, 402, 450, 509, 525, 744, 1122, 1856)
M49_TGS_PROFILES: Final = ("current_increment", "causal_rolling_1s")
_MAX_JSON_BYTES: Final = 1024 * 1024
_HASH_CHUNK_BYTES: Final = 1024 * 1024
class M49TgsFailClosedError(RuntimeError):
"""The TGS evidence pack is unavailable or failed its immutable contract."""
@dataclass(frozen=True, slots=True)
class M49TgsFailClosedResult:
result_id: str
root: Path
manifest: dict[str, Any]
report: dict[str, Any]
@property
def evidence_path(self) -> Path:
return self.root / "evidence.npz"
def seal_m49_tgs_fail_closed(
*,
source_root: Path,
destination_root: Path,
profile_path: Path,
linked_visual_result_id: str,
created_at_utc: str | None = None,
) -> M49TgsFailClosedResult:
source = _real_directory(source_root, "M49 Worker evidence")
destination = destination_root.expanduser().absolute()
destination.mkdir(parents=True, exist_ok=True)
if destination.is_symlink():
raise M49TgsFailClosedError("M49 destination must not be a symlink")
profile = _json_file(profile_path, "M49 profile")
worker_result = _json_file(source / "result.json", "M49 Worker result")
worker_summary = _json_file(source / "worker-summary.json", "M49 Worker summary")
input_manifest = _json_file(source / "input-manifest.json", "M49 input manifest")
timing = _timing_metrics(source / "tgs-timing.tsv")
_validate_source(profile, worker_result, worker_summary, input_manifest, source)
if (
not linked_visual_result_id.startswith("m4-threat-replay-")
or len(linked_visual_result_id) != len("m4-threat-replay-") + 64
):
raise M49TgsFailClosedError("M49 linked visual result is invalid")
evidence_sha = _file_sha256(source / "evidence.npz")
identity = {
"schema_version": M49_TGS_RESULT_SCHEMA,
"source": {
"source_id": "RAVNOVES00",
"source_session_id": "20260720T065719Z_viewer_live",
"source_pack_sha256": worker_result["source_pack_sha256"],
"input_manifest_sha256": worker_result["input_manifest_sha256"],
"linked_visual_result_id": linked_visual_result_id,
"anchor_frame_indices": list(M49_TGS_ANCHORS),
},
"configuration": {
"profile_id": M49_TGS_PROFILE_ID,
"config_sha256": worker_result["config_sha256"],
"coordinate_frame": "map-gravity-local",
"primary_profile": "causal_rolling_1s",
"cell_size_m": worker_result["costmap"]["cell_size_m"],
"radius_m": worker_result["costmap"]["radius_m"],
},
"method": {
"execution_class": "deterministic",
"pipeline_id": "travel-tgs-gravity-aligned-fail-closed/v1",
"travel_revision": profile["source"]["travel_revision"],
"aos_used": False,
"missing_support_means_free": False,
"eligible_point_accounting": "exact-multiset-complement",
},
"evidence": {
"sha256": evidence_sha,
"byte_length": (source / "evidence.npz").stat().st_size,
"state_codes": profile["state_codes"],
},
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
}
identity_sha256 = _canonical_sha256(identity)
result_id = f"{M49_TGS_PREFIX}{identity_sha256}"
target = destination / result_id
if target.exists():
return read_m49_tgs_fail_closed(target)
created = created_at_utc or datetime.now(tz=UTC).isoformat().replace("+00:00", "Z")
anchors = worker_result["anchors"]
primary = [row for row in anchors if row["profile_id"] == "causal_rolling_1s"]
report = {
"schema_version": M49_TGS_REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": created,
"source": identity["source"],
"configuration": {
**identity["configuration"],
"state_priority": profile["costmap"]["state_priority"],
"tgs": profile["tgs"],
},
"method": {
**identity["method"],
"components": [
{
"kind": "algorithm",
"name": "TRAVEL GroundSeg",
"version": profile["source"]["travel_revision"],
"role": "gravity-aligned ground/nonground separation",
},
{
"kind": "algorithm",
"name": "fail-closed complement adapter",
"version": "v1",
"role": "retain rejected points and explicit unobserved cells",
},
{
"kind": "runtime",
"name": "Worker 006 CPU qualification",
"version": worker_summary["code_revision"],
"role": "20 bounded TGS invocations without GPU",
},
],
},
"execution": {
"worker": "Worker 006",
"device": "cpu",
"gpu_used": False,
"wrapper_elapsed_seconds": worker_summary["wall_seconds"],
"canonical_triton_id": worker_summary["canonical_triton_id"],
"canonical_triton_health": worker_summary["canonical_triton_health"],
"free_memory_gib_before": worker_summary["free_memory_gib_before"],
},
"metrics": {
"anchor_count": len(M49_TGS_ANCHORS),
"anchor_profile_count": len(anchors),
"all_eligible_points_accounted": True,
"primary_profile": "causal_rolling_1s",
"primary": primary,
"costmap_cell_count": worker_result["costmap"]["cell_count"],
"process_wall_current_p50_ms": timing["current_increment"]["p50_ms"],
"process_wall_current_max_ms": timing["current_increment"]["max_ms"],
"process_wall_rolling_p50_ms": timing["causal_rolling_1s"]["p50_ms"],
"process_wall_rolling_max_ms": timing["causal_rolling_1s"]["max_ms"],
"process_max_rss_kib": timing["max_rss_kib"],
},
"acceptance": {
"representation_complete": True,
"all_points_accounted": True,
"aos_absent": True,
"gpu_absent": True,
"visual_quality_accepted": False,
"traversability_accepted": False,
},
"decision": {
"state": "visual-review-required",
"candidate_retained": True,
"next_action": (
"Review exact gravity-aligned points and fail-closed costmap "
"on the ten immutable anchors."
),
},
"limitations": [
"The ten anchors are bounded diagnostic evidence, not a full 4,489-frame replay.",
"No independent terrain or traversability truth is available.",
"No vehicle envelope exists, so occupied cells do not grant or deny physical passage.",
"Visual quality, realtime integration, navigation and actuation remain unaccepted.",
],
"authority": {
"mode": "replay-simulated",
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
"visual_review": {
"instrument": "m4-canonical-reference-graph",
"linked_visual_result_id": linked_visual_result_id,
"anchors": list(M49_TGS_ANCHORS),
"profiles": list(M49_TGS_PROFILES),
"default_profile": "causal_rolling_1s",
"point_states": profile["state_codes"],
},
}
with tempfile.TemporaryDirectory(prefix="mission-core-m49-tgs-", dir=destination) as raw:
staging = Path(raw) / result_id
staging.mkdir()
for name in (
"evidence.npz",
"worker-summary.json",
"input-manifest.json",
"tgs-timing.tsv",
):
shutil.copyfile(source / name, staging / name)
_write_json(staging / "report.json", report)
artifacts = [
_artifact(staging / "report.json", "report", M49_TGS_REPORT_SCHEMA, "application/json"),
_artifact(
staging / "evidence.npz", "visual-spatial-evidence", None, "application/x-npz"
),
_artifact(staging / "worker-summary.json", "runtime-summary", None, "application/json"),
_artifact(staging / "input-manifest.json", "source-manifest", None, "application/json"),
_artifact(
staging / "tgs-timing.tsv", "runtime-timing", None, "text/tab-separated-values"
),
]
manifest = {
"schema_version": M49_TGS_RESULT_SCHEMA,
"result_id": result_id,
"created_at_utc": created,
"identity_sha256": identity_sha256,
"identity": identity,
"artifacts": artifacts,
"authority": report["authority"],
"ground_truth": False,
}
_write_json(staging / "manifest.json", manifest)
os.replace(staging, target)
return read_m49_tgs_fail_closed(target)
def read_m49_tgs_fail_closed(root: Path) -> M49TgsFailClosedResult:
candidate = _real_directory(root, "M49 result")
if not candidate.name.startswith(M49_TGS_PREFIX):
raise M49TgsFailClosedError("M49 result identity is invalid")
manifest = _json_file(candidate / "manifest.json", "M49 manifest")
report = _json_file(candidate / "report.json", "M49 report")
if (
manifest.get("schema_version") != M49_TGS_RESULT_SCHEMA
or manifest.get("result_id") != candidate.name
or report.get("schema_version") != M49_TGS_REPORT_SCHEMA
or report.get("result_id") != candidate.name
):
raise M49TgsFailClosedError("M49 result contract changed")
identity = manifest.get("identity")
identity_sha = manifest.get("identity_sha256")
if (
not isinstance(identity, dict)
or not isinstance(identity_sha, str)
or _canonical_sha256(identity) != identity_sha
or candidate.name != f"{M49_TGS_PREFIX}{identity_sha}"
):
raise M49TgsFailClosedError("M49 identity proof changed")
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list) or len(artifacts) != 5:
raise M49TgsFailClosedError("M49 artifact manifest changed")
for item in artifacts:
if not isinstance(item, dict):
raise M49TgsFailClosedError("M49 artifact descriptor changed")
path = candidate / str(item.get("path", ""))
if (
path.is_symlink()
or not path.is_file()
or path.parent != candidate
or path.stat().st_size != item.get("byte_length")
or _file_sha256(path) != item.get("sha256")
):
raise M49TgsFailClosedError("M49 artifact proof changed")
return M49TgsFailClosedResult(candidate.name, candidate, manifest, report)
def _validate_source(
profile: dict[str, Any],
worker_result: dict[str, Any],
worker_summary: dict[str, Any],
input_manifest: dict[str, Any],
source: Path,
) -> None:
if (
profile.get("schema_version") != "missioncore.m49-tgs-fail-closed-evidence-profile/v1"
or profile.get("profile_id") != M49_TGS_PROFILE_ID
or tuple(profile.get("anchors", ())) != M49_TGS_ANCHORS
or profile.get("invariants", {}).get("aos_allowed") is not False
or profile.get("invariants", {}).get("missing_support_means_free") is not False
):
raise M49TgsFailClosedError("M49 profile changed")
if (
worker_result.get("schema_version") != M49_TGS_WORKER_RESULT_SCHEMA
or worker_result.get("status") != "passed"
or worker_result.get("summary", {}).get("aos_used") is not False
or worker_result.get("summary", {}).get("all_eligible_points_accounted") is not True
or worker_result.get("summary", {}).get("primary_profile") != "causal_rolling_1s"
or len(worker_result.get("anchors", ())) != 20
):
raise M49TgsFailClosedError("M49 Worker result changed")
if (
input_manifest.get("schema_version") != "missioncore.m49-tgs-fail-closed-input/v1"
or input_manifest.get("coordinate_frame") != "map-gravity-local"
or len(input_manifest.get("records", ())) != 20
):
raise M49TgsFailClosedError("M49 input manifest changed")
if (
worker_summary.get("gpu_requested") is not False
and worker_summary.get("gpu_requested") is not None
):
raise M49TgsFailClosedError("M49 Worker GPU contract changed")
if (
worker_summary.get("aos_used") is not False
or worker_summary.get("all_eligible_points_accounted") is not True
or worker_summary.get("canonical_triton_health") != "healthy"
):
raise M49TgsFailClosedError("M49 Worker cleanup changed")
evidence = worker_result.get("evidence", {})
if (
evidence.get("path") != "evidence.npz"
or evidence.get("bytes") != (source / "evidence.npz").stat().st_size
or evidence.get("sha256") != _file_sha256(source / "evidence.npz")
or worker_result.get("input_manifest_sha256")
!= _file_sha256(source / "input-manifest.json")
):
raise M49TgsFailClosedError("M49 evidence proof changed")
def _artifact(
path: Path,
role: str,
schema_version: str | None,
media_type: str,
) -> dict[str, object]:
result: dict[str, object] = {
"role": role,
"path": path.name,
"byte_length": path.stat().st_size,
"sha256": _file_sha256(path),
"media_type": media_type,
}
if schema_version is not None:
result["schema_version"] = schema_version
return result
def _timing_metrics(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise M49TgsFailClosedError("M49 timing evidence is unavailable")
profiles: dict[str, list[float]] = {name: [] for name in M49_TGS_PROFILES}
maximum_rss = 0
lines = path.read_text(encoding="utf-8-sig").splitlines()
if not lines or lines[0] != "profile\tslot\twall_seconds\tmax_rss_kib":
raise M49TgsFailClosedError("M49 timing evidence changed")
for line in lines[1:]:
fields = line.split("\t")
if len(fields) != 4 or fields[0] not in profiles:
raise M49TgsFailClosedError("M49 timing row changed")
profiles[fields[0]].append(float(fields[2]))
maximum_rss = max(maximum_rss, int(fields[3]))
if any(len(values) != len(M49_TGS_ANCHORS) for values in profiles.values()):
raise M49TgsFailClosedError("M49 timing coverage changed")
result: dict[str, Any] = {"max_rss_kib": maximum_rss}
for name, values in profiles.items():
ordered = sorted(values)
result[name] = {
"p50_ms": ordered[len(ordered) // 2] * 1000,
"max_ms": max(ordered) * 1000,
}
return result
def _real_directory(path: Path, label: str) -> Path:
candidate = path.expanduser().absolute()
if candidate.is_symlink():
raise M49TgsFailClosedError(f"{label} must not be a symlink")
try:
resolved = candidate.resolve(strict=True)
except OSError as exc:
raise M49TgsFailClosedError(f"{label} is unavailable") from exc
if not resolved.is_dir():
raise M49TgsFailClosedError(f"{label} is unavailable")
return resolved
def _json_file(path: Path, label: str) -> dict[str, Any]:
if path.is_symlink() or not path.is_file() or path.stat().st_size > _MAX_JSON_BYTES:
raise M49TgsFailClosedError(f"{label} is unavailable")
try:
value = json.loads(path.read_text(encoding="utf-8-sig"))
except (json.JSONDecodeError, OSError) as exc:
raise M49TgsFailClosedError(f"{label} is invalid") from exc
if not isinstance(value, dict):
raise M49TgsFailClosedError(f"{label} is invalid")
return value
def _write_json(path: Path, value: object) -> None:
path.write_bytes(_canonical_json(value) + b"\n")
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _canonical_sha256(value: object) -> str:
return hashlib.sha256(_canonical_json(value)).hexdigest()
def _file_sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(_HASH_CHUNK_BYTES), b""):
digest.update(chunk)
return digest.hexdigest()
__all__ = [
"M49_TGS_ANCHORS",
"M49_TGS_PREFIX",
"M49_TGS_REPORT_SCHEMA",
"M49_TGS_RESULT_SCHEMA",
"M49TgsFailClosedError",
"M49TgsFailClosedResult",
"read_m49_tgs_fail_closed",
"seal_m49_tgs_fail_closed",
]
@@ -0,0 +1,304 @@
"""Seal and verify the complete source-paced TRAVEL TGS shadow."""
from __future__ import annotations
import hashlib
import json
import shutil
import tempfile
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Final
RESULT_SCHEMA: Final = "missioncore.m49-tgs-full-shadow-lab/v1"
REPORT_SCHEMA: Final = "missioncore.m49-tgs-full-shadow-report/v1"
WORKER_SCHEMA: Final = "missioncore.m49-tgs-full-shadow-result/v1"
PREFIX: Final = "m49-tgs-full-shadow-"
PROFILE_SCHEMA: Final = "missioncore.m49-tgs-full-shadow-profile/v1"
EVIDENCE_FILES: Final = (
"costmap-cell-centers-xy-m.npy",
"costmap-cell-indices-xy.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
)
_HASH_CHUNK_BYTES: Final = 1024 * 1024
_MAX_JSON_BYTES: Final = 4 * 1024 * 1024
class M49TgsFullShadowError(RuntimeError):
"""The full TGS shadow is unavailable or violates its immutable contract."""
@dataclass(frozen=True, slots=True)
class M49TgsFullShadowResult:
result_id: str
root: Path
manifest: dict[str, Any]
report: dict[str, Any]
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(_HASH_CHUNK_BYTES), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_sha256(value: object) -> str:
content = json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
).encode("utf-8")
return hashlib.sha256(content).hexdigest()
def _json(path: Path, label: str) -> dict[str, Any]:
if path.is_symlink() or not path.is_file() or path.stat().st_size > _MAX_JSON_BYTES:
raise M49TgsFullShadowError(f"{label} is unavailable")
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise M49TgsFullShadowError(f"{label} is invalid")
return value
def _artifact(path: Path, role: str, media_type: str) -> dict[str, object]:
return {
"path": path.name,
"role": role,
"media_type": media_type,
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
def seal_m49_tgs_full_shadow(
*,
source_root: Path,
destination_root: Path,
profile_path: Path,
linked_visual_result_id: str,
linked_semantic_result_id: str,
created_at_utc: str | None = None,
) -> M49TgsFullShadowResult:
source = source_root.expanduser().resolve(strict=True)
if source.is_symlink() or not source.is_dir():
raise M49TgsFullShadowError("Worker evidence root is unavailable")
destination = destination_root.expanduser().absolute()
destination.mkdir(parents=True, exist_ok=True)
if destination.is_symlink():
raise M49TgsFullShadowError("destination must not be a symlink")
profile = _json(profile_path, "full-shadow profile")
worker = _json(source / "result.json", "Worker result")
summary = _json(source / "worker-summary.json", "Worker summary")
timeline = worker.get("timeline")
if (
profile.get("schema_version") != PROFILE_SCHEMA
or worker.get("schema_version") != WORKER_SCHEMA
or not isinstance(timeline, dict)
or (
timeline.get("frame_count") != 4489
or timeline.get("available_lidar_frame_count") != 3928
or timeline.get("missing_lidar_frame_count") != 561
)
or worker.get("point_accounting", {}).get("unaccounted") != 0
or summary.get("schema_version") != "missioncore.m49-tgs-full-shadow-worker-summary/v1"
or summary.get("gpu_requested") is not False
or summary.get("aos_used") is not False
or summary.get("all_timeline_frames_accounted") is not True
or summary.get("all_eligible_points_accounted") is not True
or summary.get("canonical_triton_health") != "healthy"
):
raise M49TgsFullShadowError("Worker full-shadow contract changed")
if (
not linked_visual_result_id.startswith("m4-threat-replay-")
or len(linked_visual_result_id) != len("m4-threat-replay-") + 64
):
raise M49TgsFullShadowError("linked visual result is invalid")
if (
not linked_semantic_result_id.startswith("e47-semantic-slam-")
or len(linked_semantic_result_id) != len("e47-semantic-slam-") + 64
):
raise M49TgsFullShadowError("linked semantic result is invalid")
for name in EVIDENCE_FILES:
path = source / name
proof = worker.get("files", {}).get(name, {})
if (
path.is_symlink()
or not path.is_file()
or proof.get("bytes") != path.stat().st_size
or proof.get("sha256") != _sha256(path)
):
raise M49TgsFullShadowError(f"Worker evidence changed: {name}")
identity = {
"schema_version": RESULT_SCHEMA,
"source_pack_sha256": worker["source_pack_sha256"],
"input_manifest_sha256": worker["input_manifest_sha256"],
"config_sha256": worker["config_sha256"],
"linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
"files": {name: worker["files"][name]["sha256"] for name in EVIDENCE_FILES},
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
}
identity_sha256 = _canonical_sha256(identity)
result_id = f"{PREFIX}{identity_sha256}"
target = destination / result_id
if target.exists():
return read_m49_tgs_full_shadow(target)
created = created_at_utc or datetime.now(tz=UTC).isoformat().replace("+00:00", "Z")
performance_accepted = worker.get("status") == "passed"
report = {
"schema_version": REPORT_SCHEMA,
"result_id": result_id,
"created_at_utc": created,
"source": {
"source_id": "RAVNOVES00",
"source_session_id": "20260720T065719Z_viewer_live",
"source_pack_sha256": worker["source_pack_sha256"],
"linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
},
"configuration": {
"profile_id": profile["profile_id"],
"config_sha256": worker["config_sha256"],
"coordinate_frame": "map-gravity-local",
"history_seconds": profile["profile"]["history_seconds"],
"cell_size_m": worker["costmap"]["cell_size_m"],
"radius_m": worker["costmap"]["radius_m"],
"state_priority": profile["costmap"]["state_priority"],
},
"execution": {
"worker": "Worker 006",
"device": "cpu",
"gpu_used": False,
"aos_used": False,
"wrapper_elapsed_seconds": summary["wall_seconds"],
"canonical_triton_id": summary["canonical_triton_id"],
"canonical_triton_health": summary["canonical_triton_health"],
},
"timeline": worker["timeline"],
"point_accounting": worker["point_accounting"],
"performance": worker["performance"],
"acceptance": {
**worker["acceptance"],
"representation_complete": True,
"visual_quality_accepted": False,
"integrated_graph_performance_accepted": False,
},
"decision": {
"state": (
"source-paced-qualified-visual-review-required"
if performance_accepted
else "performance-rejected"
),
"candidate_retained": performance_accepted,
"next_action": (
"Review the complete camera-synchronised TGS costmap timeline; "
"then measure the integrated graph regression separately."
),
},
"limitations": [
"The run proves recorded source-paced CPU shadow performance, not live sensor transport.",
"No independent traversability truth or vehicle envelope is present.",
"Missing LiDAR frames are explicit all-cell UNOBSERVED and never inferred free.",
"Camera projection, navigation and actuation remain disabled.",
],
"authority": {
"mode": "replay-simulated",
"commands_enabled": False,
"realtime_shadow_accepted": performance_accepted,
"integrated_graph_performance_accepted": False,
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
"visual_review": {
"instrument": "m4-canonical-reference-graph",
"linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
"frame_count": 4489,
"state_codes": profile["state_codes"],
},
}
with tempfile.TemporaryDirectory(prefix="mission-core-m49-tgs-full-", dir=destination) as raw:
staging = Path(raw) / result_id
staging.mkdir()
for name in EVIDENCE_FILES:
shutil.copyfile(source / name, staging / name)
shutil.copyfile(source / "worker-summary.json", staging / "worker-summary.json")
(staging / "report.json").write_text(
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
artifacts = [
_artifact(staging / "report.json", "report", "application/json"),
_artifact(staging / "worker-summary.json", "runtime-summary", "application/json"),
]
artifacts.extend(
_artifact(
staging / name,
"frame-catalog" if name == "frames.ndjson" else "spatial-evidence",
"application/x-ndjson" if name == "frames.ndjson" else "application/x-npy",
)
for name in EVIDENCE_FILES
)
manifest = {
"schema_version": RESULT_SCHEMA,
"result_id": result_id,
"created_at_utc": created,
"identity_sha256": identity_sha256,
"identity": identity,
"artifacts": artifacts,
}
(staging / "manifest.json").write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
staging.replace(target)
return read_m49_tgs_full_shadow(target)
def read_m49_tgs_full_shadow(root: Path) -> M49TgsFullShadowResult:
candidate = root.expanduser().resolve(strict=True)
if candidate.is_symlink() or not candidate.is_dir() or not candidate.name.startswith(PREFIX):
raise M49TgsFullShadowError("full-shadow result root is invalid")
manifest = _json(candidate / "manifest.json", "full-shadow manifest")
report = _json(candidate / "report.json", "full-shadow report")
identity = manifest.get("identity")
if (
manifest.get("schema_version") != RESULT_SCHEMA
or report.get("schema_version") != REPORT_SCHEMA
or manifest.get("result_id") != candidate.name
or report.get("result_id") != candidate.name
or not isinstance(identity, dict)
or manifest.get("identity_sha256") != _canonical_sha256(identity)
or candidate.name != f"{PREFIX}{manifest['identity_sha256']}"
):
raise M49TgsFullShadowError("full-shadow identity changed")
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list):
raise M49TgsFullShadowError("full-shadow artifact catalog changed")
for artifact in artifacts:
if not isinstance(artifact, dict) or not isinstance(artifact.get("path"), str):
raise M49TgsFullShadowError("full-shadow artifact entry changed")
path = candidate / artifact["path"]
if (
path.parent != candidate
or path.is_symlink()
or not path.is_file()
or artifact.get("byte_length") != path.stat().st_size
or artifact.get("sha256") != _sha256(path)
):
raise M49TgsFullShadowError("full-shadow artifact digest changed")
return M49TgsFullShadowResult(candidate.name, candidate, manifest, report)
__all__ = [
"M49TgsFullShadowError",
"M49TgsFullShadowResult",
"PREFIX",
"read_m49_tgs_full_shadow",
"seal_m49_tgs_full_shadow",
]
+24
View File
@@ -133,6 +133,8 @@ from k1link.web.m48s_fixed_class_detector_lab_api import (
build_m48s_fixed_class_detector_lab_router,
)
from k1link.web.m48t_risk_quality_lab_api import build_m48t_risk_quality_lab_router
from k1link.web.m49_tgs_fail_closed_api import build_m49_tgs_fail_closed_router
from k1link.web.m49_tgs_full_shadow_api import build_m49_tgs_full_shadow_router
from k1link.web.map_api import (
MapGatewayConfiguration,
MapGatewayProxy,
@@ -992,6 +994,28 @@ app.include_router(
),
)
)
app.include_router(
build_m49_tgs_fail_closed_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "m49"
/ "tgs-fail-closed-results"
),
)
)
app.include_router(
build_m49_tgs_full_shadow_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "m49"
/ "tgs-full-shadow-results"
),
)
)
app.include_router(
build_m48s_fixed_class_detector_lab_router(
root_provider=lambda: (
+284
View File
@@ -0,0 +1,284 @@
"""Read-only API for sealed gravity-aligned M49 TGS evidence."""
from __future__ import annotations
import copy
import json
import math
import re
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from typing import Final
import numpy as np
from fastapi import APIRouter, HTTPException, Query, Response
from k1link.laboratory.m49_tgs_fail_closed import (
M49_TGS_ANCHORS,
M49_TGS_PREFIX,
M49TgsFailClosedError,
M49TgsFailClosedResult,
read_m49_tgs_fail_closed,
)
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(M49_TGS_PREFIX)}[a-f0-9]{{64}}$")
RESULT_VIEW_SCHEMA: Final = "missioncore.m49-tgs-fail-closed-view/v1"
RESULT_CATALOG_SCHEMA: Final = "missioncore.m49-tgs-fail-closed-catalog/v1"
ANCHOR_CATALOG_SCHEMA: Final = "missioncore.m49-tgs-anchor-catalog/v1"
ANCHOR_SPATIAL_SCHEMA: Final = "missioncore.m49-tgs-anchor-spatial/v1"
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-fail-closed"
PROFILES: Final = ("current_increment", "causal_rolling_1s")
def build_m49_tgs_fail_closed_router(*, root_provider: RootProvider = lambda: None) -> APIRouter:
router = APIRouter(prefix=ENDPOINT_ROOT, tags=["laboratory"])
def result(result_id: str) -> M49TgsFailClosedResult:
candidate = _resolve_candidate(root_provider, result_id)
try:
return _read_result_cached(str(candidate), _signature(candidate))
except (M49TgsFailClosedError, OSError, ValueError):
raise HTTPException(status_code=404, detail="M49 TGS result not found") from None
@router.get("/results")
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
root = _configured_root(root_provider)
if root is None:
return _empty_catalog(configured=False)
items: list[dict[str, object]] = []
invalid_total = 0
for candidate in sorted(root.iterdir()):
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue
try:
sealed = _read_result_cached(str(candidate.resolve()), _signature(candidate))
items.append(_project_result(sealed))
except (M49TgsFailClosedError, OSError, ValueError):
invalid_total += 1
items.sort(
key=lambda item: (str(item["created_at_utc"]), str(item["result_id"])),
reverse=True,
)
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": True,
"items": items[:limit],
"candidate_total": len(items) + invalid_total,
"invalid_total": invalid_total,
"access": "read-only",
}
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
return _project_result(result(result_id))
@router.get("/{result_id}/anchors")
def get_anchors(result_id: str) -> dict[str, object]:
sealed = result(result_id)
return {
"schema_version": ANCHOR_CATALOG_SCHEMA,
"result_id": result_id,
"linked_visual_result_id": sealed.report["visual_review"]["linked_visual_result_id"],
"anchors": copy.deepcopy(sealed.report["metrics"]["primary"]),
"anchor_count": len(M49_TGS_ANCHORS),
"profiles": list(PROFILES),
"default_profile": "causal_rolling_1s",
"access": "read-only",
}
@router.get("/{result_id}/anchors/{anchor_frame_index}/spatial")
def get_anchor_spatial(
result_id: str,
anchor_frame_index: int,
profile: str = Query(default="causal_rolling_1s"),
) -> Response:
if profile not in PROFILES or anchor_frame_index not in M49_TGS_ANCHORS:
raise HTTPException(status_code=404, detail="M49 TGS anchor not found")
sealed = result(result_id)
try:
content = _anchor_json_cached(
str(sealed.evidence_path),
result_id,
anchor_frame_index,
profile,
_evidence_signature(sealed.evidence_path),
)
except (KeyError, OSError, ValueError):
raise HTTPException(
status_code=503, detail="M49 TGS spatial evidence failed verification"
) from None
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
return router
@lru_cache(maxsize=4)
def _read_result_cached(result_root: str, signature: tuple[int, ...]) -> M49TgsFailClosedResult:
del signature
return read_m49_tgs_fail_closed(Path(result_root))
@lru_cache(maxsize=24)
def _anchor_json_cached(
evidence_path: str,
result_id: str,
anchor_frame_index: int,
profile: str,
signature: tuple[int, int],
) -> bytes:
del signature
slot = M49_TGS_ANCHORS.index(anchor_frame_index)
with np.load(evidence_path, allow_pickle=False) as evidence:
offsets = evidence[f"{profile}_point_offsets"]
start = int(offsets[slot])
end = int(offsets[slot + 1])
points = evidence[f"{profile}_points_xyz_m"][start:end]
point_states = evidence[f"{profile}_point_states"][start:end]
centers = evidence["costmap_cell_centers_xy_m"]
cell_states = evidence[f"{profile}_costmap_states"][slot]
z_bounds = evidence[f"{profile}_costmap_z_bounds_m"][slot]
if (
points.shape[1:] != (3,)
or point_states.shape != (points.shape[0],)
or centers.shape != (2244, 2)
or cell_states.shape != (2244,)
or z_bounds.shape != (2244, 2)
or not np.isfinite(points).all()
or not np.isfinite(centers).all()
or not np.isin(point_states, np.asarray([1, 2, 3], dtype=np.uint8)).all()
or not np.isin(cell_states, np.asarray([0, 1, 2, 3], dtype=np.uint8)).all()
):
raise ValueError("M49 TGS spatial shape changed")
payload = {
"schema_version": ANCHOR_SPATIAL_SCHEMA,
"result_id": result_id,
"anchor_frame_index": anchor_frame_index,
"source_sequence": anchor_frame_index,
"profile": profile,
"coordinate_frame": "map-gravity-local",
"points_xyz_m": points.astype(float).tolist(),
"point_states": point_states.astype(int).tolist(),
"costmap": {
"cell_size_m": 0.45,
"radius_m": 12.0,
"centers_xy_m": centers.astype(float).tolist(),
"states": cell_states.astype(int).tolist(),
"z_bounds_m": [
[
float(row[0]) if math.isfinite(float(row[0])) else None,
float(row[1]) if math.isfinite(float(row[1])) else None,
]
for row in z_bounds
],
},
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
},
"all_points_accounted": True,
"aos_used": False,
"authority": {
"visual_quality_accepted": False,
"navigation_or_safety_accepted": False,
},
"access": "read-only",
}
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _project_result(result: M49TgsFailClosedResult) -> dict[str, object]:
report = result.report
return {
"schema_version": RESULT_VIEW_SCHEMA,
"result_id": result.result_id,
"created_at_utc": result.manifest["created_at_utc"],
"source": copy.deepcopy(report["source"]),
"configuration": copy.deepcopy(report["configuration"]),
"method": copy.deepcopy(report["method"]),
"execution": copy.deepcopy(report["execution"]),
"metrics": copy.deepcopy(report["metrics"]),
"acceptance": copy.deepcopy(report["acceptance"]),
"decision": copy.deepcopy(report["decision"]),
"limitations": copy.deepcopy(report["limitations"]),
"authority": copy.deepcopy(report["authority"]),
"visual_review": copy.deepcopy(report["visual_review"]),
"ground_truth": False,
"access": "read-only",
}
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
if RESULT_ID.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="M49 TGS result not found")
root = _configured_root(provider)
if root is None:
raise HTTPException(status_code=404, detail="M49 TGS result not found")
candidate = root / result_id
if candidate.is_symlink() or not candidate.is_dir():
raise HTTPException(status_code=404, detail="M49 TGS result not found")
resolved = candidate.resolve(strict=True)
if resolved.parent != root:
raise HTTPException(status_code=404, detail="M49 TGS result not found")
return resolved
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None or value.is_symlink() or not value.is_dir():
return None
return value.resolve(strict=True)
def _signature(candidate: Path) -> tuple[int, ...]:
result: list[int] = []
for name in (
"manifest.json",
"report.json",
"evidence.npz",
"worker-summary.json",
"input-manifest.json",
"tgs-timing.tsv",
):
path = candidate / name
if path.is_symlink() or not path.is_file():
raise ValueError("M49 TGS artifact unavailable")
stat = path.stat()
result.extend((stat.st_size, stat.st_mtime_ns))
return tuple(result)
def _evidence_signature(path: Path) -> tuple[int, int]:
stat = path.stat()
return stat.st_size, stat.st_mtime_ns
def _empty_catalog(*, configured: bool) -> dict[str, object]:
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": configured,
"items": [],
"candidate_total": 0,
"invalid_total": 0,
"access": "read-only",
}
__all__ = ["build_m49_tgs_fail_closed_router"]
+314
View File
@@ -0,0 +1,314 @@
"""Read-only API for the sealed complete TGS shadow."""
from __future__ import annotations
import copy
import json
import math
import re
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from typing import Final
import numpy as np
from fastapi import APIRouter, HTTPException, Query, Response
from k1link.laboratory.m49_tgs_full_shadow import (
M49TgsFullShadowError,
M49TgsFullShadowResult,
PREFIX,
read_m49_tgs_full_shadow,
)
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(PREFIX)}[a-f0-9]{{64}}$")
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-full-shadow"
def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: None) -> APIRouter:
router = APIRouter(prefix=ENDPOINT_ROOT, tags=["laboratory"])
def sealed(result_id: str) -> M49TgsFullShadowResult:
root = _configured_root(root_provider)
if root is None or RESULT_ID.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found")
candidate = root / result_id
if candidate.is_symlink() or not candidate.is_dir():
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found")
try:
resolved = candidate.resolve(strict=True)
if resolved.parent != root:
raise ValueError("result escaped configured root")
return _read_cached(str(resolved), _signature(resolved))
except (M49TgsFullShadowError, OSError, ValueError):
raise HTTPException(status_code=404, detail="M49 TGS full shadow not found") from None
@router.get("/results")
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
root = _configured_root(root_provider)
if root is None:
return _catalog([], configured=False, invalid_total=0)
results: list[dict[str, object]] = []
invalid = 0
for candidate in sorted(root.iterdir()):
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue
try:
results.append(_project(_read_cached(str(candidate.resolve()), _signature(candidate))))
except (M49TgsFullShadowError, OSError, ValueError):
invalid += 1
results.sort(key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True)
return _catalog(results[:limit], configured=True, invalid_total=invalid)
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
return _project(sealed(result_id))
@router.get("/{result_id}/frames/{source_sequence}/spatial")
def get_spatial(result_id: str, source_sequence: int) -> Response:
if source_sequence < 0 or source_sequence >= 4489:
raise HTTPException(status_code=404, detail="M49 TGS full-shadow frame not found")
result = sealed(result_id)
try:
content = _frame_json_cached(
str(result.root), result_id, source_sequence, _evidence_signature(result.root)
)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification") from None
return Response(
content=content,
media_type="application/json",
headers={"Cache-Control": "private, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff"},
)
@router.get("/{result_id}/spatial/chunk")
def get_spatial_chunk(
result_id: str,
start: int = Query(ge=0, lt=4489),
count: int = Query(default=24, ge=1, le=24),
) -> Response:
result = sealed(result_id)
bounded_count = min(count, 4489 - start)
try:
content = _chunk_json_cached(
str(result.root), result_id, start, bounded_count, _evidence_signature(result.root)
)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(
status_code=503,
detail="M49 TGS full-shadow spatial chunk failed verification",
) from None
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
return router
@lru_cache(maxsize=4)
def _read_cached(root: str, signature: tuple[int, ...]) -> M49TgsFullShadowResult:
del signature
return read_m49_tgs_full_shadow(Path(root))
@lru_cache(maxsize=4)
def _frames(root: str, signature: tuple[int, int]) -> tuple[dict[str, object], ...]:
del signature
path = Path(root) / "frames.ndjson"
rows = tuple(json.loads(line) for line in path.read_text(encoding="utf-8").splitlines())
if len(rows) != 4489:
raise ValueError("full-shadow frame catalog changed")
return rows
@lru_cache(maxsize=96)
def _frame_json_cached(
root: str, result_id: str, source_sequence: int, signature: tuple[int, ...]
) -> bytes:
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frame = _frames(root, frame_signature)[source_sequence]
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
states = states_all[source_sequence]
z_bounds = z_all[source_sequence]
if (
centers.shape != (2244, 2)
or states_all.shape != (4489, 2244)
or z_all.shape != (4489, 2244, 2)
or not np.isfinite(centers).all()
or not np.isin(states, np.asarray([0, 1, 2, 3], dtype=np.uint8)).all()
):
raise ValueError("full-shadow spatial shape changed")
payload = {
"schema_version": "missioncore.m49-tgs-full-shadow-spatial/v1",
"result_id": result_id,
"source_sequence": source_sequence,
"source_frame_index": frame["source_frame_index"],
"session_seconds": frame["session_seconds"],
"sample_available": frame["sample_available"],
"coordinate_frame": "map-gravity-local",
"costmap": {
"cell_size_m": 0.45,
"radius_m": 12.0,
"centers_xy_m": centers.astype(float).tolist(),
"states": states.astype(int).tolist(),
"z_bounds_m": [
[
float(row[0]) if math.isfinite(float(row[0])) else None,
float(row[1]) if math.isfinite(float(row[1])) else None,
]
for row in z_bounds
],
},
"metrics": copy.deepcopy(frame),
"state_codes": {"UNOBSERVED": 0, "GROUND_SUPPORT": 1, "NONGROUND_OCCUPIED": 2, "UNKNOWN_REJECTED": 3},
"aos_used": False,
"gpu_used": False,
"authority": {"navigation_or_safety_accepted": False, "visual_quality_accepted": False},
"access": "read-only",
}
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
@lru_cache(maxsize=16)
def _chunk_json_cached(
root: str,
result_id: str,
start: int,
count: int,
signature: tuple[int, ...],
) -> bytes:
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frames = _frames(root, frame_signature)
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
if (
centers.shape != (2244, 2)
or states_all.shape != (4489, 2244)
or z_all.shape != (4489, 2244, 2)
or not np.isfinite(centers).all()
):
raise ValueError("full-shadow spatial chunk shape changed")
rows: list[dict[str, object]] = []
for source_sequence in range(start, start + count):
frame = frames[source_sequence]
states = states_all[source_sequence]
z_bounds = z_all[source_sequence]
if not np.isin(states, np.asarray([0, 1, 2, 3], dtype=np.uint8)).all():
raise ValueError("full-shadow spatial state changed")
rows.append(
{
"source_sequence": source_sequence,
"source_frame_index": frame["source_frame_index"],
"session_seconds": frame["session_seconds"],
"sample_available": frame["sample_available"],
"states": states.astype(int).tolist(),
"z_bounds_m": [
[
float(row[0]) if math.isfinite(float(row[0])) else None,
float(row[1]) if math.isfinite(float(row[1])) else None,
]
for row in z_bounds
],
"metrics": copy.deepcopy(frame),
}
)
payload = {
"schema_version": "missioncore.m49-tgs-full-shadow-spatial-chunk/v1",
"result_id": result_id,
"start": start,
"count": count,
"coordinate_frame": "map-gravity-local",
"costmap": {
"cell_size_m": 0.45,
"radius_m": 12.0,
"centers_xy_m": centers.astype(float).tolist(),
},
"frames": rows,
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
},
"aos_used": False,
"gpu_used": False,
"authority": {
"navigation_or_safety_accepted": False,
"visual_quality_accepted": False,
},
"access": "read-only",
}
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
return {
**copy.deepcopy(result.report),
"schema_version": "missioncore.m49-tgs-full-shadow-view/v1",
"result_id": result.result_id,
"created_at_utc": result.manifest["created_at_utc"],
"ground_truth": False,
"access": "read-only",
}
def _catalog(items: list[dict[str, object]], *, configured: bool, invalid_total: int) -> dict[str, object]:
return {
"schema_version": "missioncore.m49-tgs-full-shadow-catalog/v1",
"configured": configured,
"items": items,
"candidate_total": len(items) + invalid_total,
"invalid_total": invalid_total,
"access": "read-only",
}
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None or value.is_symlink() or not value.is_dir():
return None
return value.resolve(strict=True)
def _signature(root: Path) -> tuple[int, ...]:
values: list[int] = []
for name in ("manifest.json", "report.json", "worker-summary.json", *EVIDENCE_FILES):
path = root / name
if path.is_symlink() or not path.is_file():
raise ValueError("full-shadow artifact unavailable")
stat = path.stat()
values.extend((stat.st_size, stat.st_mtime_ns))
return tuple(values)
def _evidence_signature(root: Path) -> tuple[int, ...]:
return _signature(root)
EVIDENCE_FILES: Final = (
"costmap-cell-centers-xy-m.npy",
"costmap-cell-indices-xy.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
)
__all__ = ["build_m49_tgs_full_shadow_router"]
+3 -1
View File
@@ -127,7 +127,7 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
repository_root / "config" / "laboratories"
)
assert len(registry.definitions) == 39
assert len(registry.definitions) == 42
assert {item.work_id for item in registry.definitions} >= {
"e31-source-binding",
"e46j-raw-fisheye-realtime",
@@ -144,6 +144,8 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
"m48-static-occupancy-qualification",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
}
m48 = next(
item for item in registry.definitions if item.work_id == "m48-object-centric-quality"
+8
View File
@@ -101,6 +101,8 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
"e47-semantic-slam-shadow",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
}
by_work_id = {row.work_id: row for row in execution.definitions}
assert by_work_id["m48-small-static-passage-regression"].evidence_contract == (
@@ -118,6 +120,10 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
assert by_work_id["m48s-fixed-class-detector"].isolation == "bounded-adapter"
assert by_work_id["m48t-risk-quality-temporal"].lifecycle == "experimental"
assert by_work_id["m48t-risk-quality-temporal"].isolation == "bounded-adapter"
assert by_work_id["m49-tgs-fail-closed-evidence"].lifecycle == "experimental"
assert by_work_id["m49-tgs-fail-closed-evidence"].isolation == "bounded-adapter"
assert by_work_id["m49-tgs-full-shadow"].lifecycle == "experimental"
assert by_work_id["m49-tgs-full-shadow"].isolation == "bounded-adapter"
assert all(
row.lifecycle == "canonical"
for row in execution.definitions
@@ -126,6 +132,8 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
"e47-semantic-slam-shadow",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
}
)
assert len(execution.definitions) + len(execution.legacy_work_ids) == len(
@@ -80,7 +80,7 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
root / "config" / "laboratory-value-review.json"
)
assert len(registry.entries) == 38
assert len(registry.entries) == 40
assert {entry.catalog_id for entry in registry.entries} >= {
"e28-local-surface",
"e46d-temporal-failure-audit",
@@ -91,4 +91,6 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
"m48-static-occupancy-qualification",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
}
+165
View File
@@ -0,0 +1,165 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import numpy as np
from fastapi import FastAPI
from fastapi.testclient import TestClient
from k1link.laboratory.m49_tgs_fail_closed import (
M49_TGS_ANCHORS,
read_m49_tgs_fail_closed,
seal_m49_tgs_fail_closed,
)
from k1link.web.m49_tgs_fail_closed_api import build_m49_tgs_fail_closed_router
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def _sha(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _write_json(path: Path, value: object) -> None:
path.write_text(json.dumps(value, sort_keys=True), encoding="utf-8")
def _source(root: Path) -> Path:
root.mkdir()
centers = np.zeros((2244, 2), dtype=np.float32)
arrays: dict[str, np.ndarray] = {"costmap_cell_centers_xy_m": centers}
summaries: list[dict[str, object]] = []
for profile in ("current_increment", "causal_rolling_1s"):
arrays[f"{profile}_points_xyz_m"] = np.asarray(
[[float(index), 0.0, 0.1] for index in range(10)], dtype=np.float32
)
arrays[f"{profile}_point_states"] = np.asarray([1, 2] * 5, dtype=np.uint8)
arrays[f"{profile}_point_offsets"] = np.arange(11, dtype=np.int64)
arrays[f"{profile}_costmap_states"] = np.zeros((10, 2244), dtype=np.uint8)
arrays[f"{profile}_costmap_ground_point_counts"] = np.zeros((10, 2244), dtype=np.int32)
arrays[f"{profile}_costmap_nonground_point_counts"] = np.zeros((10, 2244), dtype=np.int32)
arrays[f"{profile}_costmap_rejected_point_counts"] = np.zeros((10, 2244), dtype=np.int32)
arrays[f"{profile}_costmap_z_bounds_m"] = np.full((10, 2244, 2), np.nan, dtype=np.float32)
for slot, anchor in enumerate(M49_TGS_ANCHORS):
summaries.append(
{
"profile_id": profile,
"slot": slot,
"anchor_frame_index": anchor,
"point_count": 1,
"ground_point_count": 1 if slot % 2 == 0 else 0,
"nonground_point_count": 0 if slot % 2 == 0 else 1,
"rejected_point_count": 0,
"ground_cell_count": 0,
"nonground_cell_count": 0,
"rejected_cell_count": 0,
"unobserved_cell_count": 2244,
"all_points_accounted": True,
}
)
np.savez_compressed(root / "evidence.npz", **arrays)
records = [
{
"profile_id": profile,
"slot": slot,
"anchor_frame_index": anchor,
}
for profile in ("current_increment", "causal_rolling_1s")
for slot, anchor in enumerate(M49_TGS_ANCHORS)
]
_write_json(
root / "input-manifest.json",
{
"schema_version": "missioncore.m49-tgs-fail-closed-input/v1",
"coordinate_frame": "map-gravity-local",
"records": records,
},
)
_write_json(
root / "worker-summary.json",
{
"schema_version": "missioncore.m49-tgs-worker-summary/v1",
"code_revision": "a" * 40,
"wall_seconds": 1.5,
"free_memory_gib_before": 42.0,
"canonical_triton_id": "b" * 64,
"canonical_triton_health": "healthy",
"all_eligible_points_accounted": True,
"aos_used": False,
},
)
timing = ["profile\tslot\twall_seconds\tmax_rss_kib"]
timing.extend(
f"{profile}\t{slot}\t0.02\t7000"
for profile in ("current_increment", "causal_rolling_1s")
for slot in range(10)
)
(root / "tgs-timing.tsv").write_text("\n".join(timing) + "\n", encoding="utf-8")
_write_json(
root / "result.json",
{
"schema_version": "missioncore.m49-tgs-fail-closed-evidence-result/v1",
"status": "passed",
"config_sha256": "c" * 64,
"source_pack_sha256": "d" * 64,
"input_manifest_sha256": _sha(root / "input-manifest.json"),
"evidence": {
"path": "evidence.npz",
"bytes": (root / "evidence.npz").stat().st_size,
"sha256": _sha(root / "evidence.npz"),
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": 0.45,
"radius_m": 12.0,
"cell_count": 2244,
},
"summary": {
"aos_used": False,
"all_eligible_points_accounted": True,
"primary_profile": "causal_rolling_1s",
},
"anchors": summaries,
},
)
return root
def test_m49_tgs_seal_and_api_are_immutable_and_fail_closed(tmp_path: Path) -> None:
source = _source(tmp_path / "source")
destination = tmp_path / "results"
linked = "m4-threat-replay-" + "e" * 64
sealed = seal_m49_tgs_fail_closed(
source_root=source,
destination_root=destination,
profile_path=(
REPOSITORY_ROOT / "config" / "perception" / "m49-tgs-fail-closed-evidence-v1.json"
),
linked_visual_result_id=linked,
created_at_utc="2026-08-26T17:30:00Z",
)
assert read_m49_tgs_fail_closed(sealed.root).result_id == sealed.result_id
app = FastAPI()
app.include_router(build_m49_tgs_fail_closed_router(root_provider=lambda: destination))
client = TestClient(app)
catalog = client.get("/api/v1/laboratory/m49/tgs-fail-closed/results")
assert catalog.status_code == 200
assert catalog.json()["items"][0]["result_id"] == sealed.result_id
anchors = client.get(f"/api/v1/laboratory/m49/tgs-fail-closed/{sealed.result_id}/anchors")
assert anchors.status_code == 200
assert anchors.json()["anchor_count"] == 10
spatial = client.get(
f"/api/v1/laboratory/m49/tgs-fail-closed/{sealed.result_id}/anchors/171/spatial"
)
assert spatial.status_code == 200
assert spatial.json()["point_states"] == [1]
assert spatial.json()["costmap"]["states"] == [0] * 2244
assert spatial.json()["authority"]["navigation_or_safety_accepted"] is False
(sealed.root / "worker-summary.json").write_text("{}", encoding="utf-8")
assert (
client.get(f"/api/v1/laboratory/m49/tgs-fail-closed/{sealed.result_id}").status_code == 404
)
+178
View File
@@ -0,0 +1,178 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import sys
from pathlib import Path
import numpy as np
from k1link.laboratory.m49_tgs_full_shadow import seal_m49_tgs_full_shadow
from k1link.web import m49_tgs_full_shadow_api as full_shadow_api
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
WORKER_ROOT = REPOSITORY_ROOT / "experiments" / "perception" / "worker" / "m49_t3_travel"
sys.path.insert(0, str(WORKER_ROOT))
EVIDENCE_SPEC = importlib.util.spec_from_file_location(
"m49_tgs_full_shadow_evidence", WORKER_ROOT / "build_tgs_full_shadow_evidence.py"
)
assert EVIDENCE_SPEC and EVIDENCE_SPEC.loader
EVIDENCE = importlib.util.module_from_spec(EVIDENCE_SPEC)
EVIDENCE_SPEC.loader.exec_module(EVIDENCE)
ARTIFACT_SPEC = importlib.util.spec_from_file_location(
"m49_tgs_full_shadow_artifact",
REPOSITORY_ROOT / "scripts" / "build_m49_tgs_full_shadow_worker_artifact.py",
)
assert ARTIFACT_SPEC and ARTIFACT_SPEC.loader
ARTIFACT = importlib.util.module_from_spec(ARTIFACT_SPEC)
ARTIFACT_SPEC.loader.exec_module(ARTIFACT)
def test_full_shadow_exact_multiset_and_costmap_priority() -> None:
native = np.asarray(
[[2.0, 0.0, 0.0, 0.0], [2.0, 0.0, 0.0, 0.0], [3.0, 0.0, 1.0, 0.0], [4.0, 0.0, 2.0, 0.0]],
dtype=np.float32,
)
points, states = EVIDENCE.classify_exact_input(native, native[[0]], native[[1, 2]])
assert sorted(states.tolist()) == [1, 2, 2, 3]
grid = EVIDENCE.costmap_grid(12.0, 0.45)
cell_states, _ = EVIDENCE.rasterize(points, states, grid, 0.45)
assert np.count_nonzero(cell_states == 2) == 2
def test_full_shadow_worker_artifact_is_deterministic_and_cpu_only(tmp_path: Path) -> None:
revision = "a" * 40
first = ARTIFACT.build("m49-tgs-full-test", tmp_path / "one", revision=revision)
second = ARTIFACT.build("m49-tgs-full-test", tmp_path / "two", revision=revision)
assert first["sha256"] == second["sha256"]
import tarfile
with tarfile.open(first["artifact"], "r:gz") as archive:
release = archive.extractfile("payload/release.json")
runner = archive.extractfile("payload/Invoke-M49TgsFullShadow.ps1")
assert release is not None and runner is not None
release_text = release.read().decode()
runner_text = runner.read().decode()
assert '"candidate_id": "travel-tgs-full-shadow"' in release_text
assert "--gpus" not in runner_text
assert "gpu_requested = $false" in runner_text
def test_full_shadow_seal_binds_visual_and_semantic_timelines(tmp_path: Path) -> None:
source = tmp_path / "worker"
source.mkdir()
files: dict[str, dict[str, object]] = {}
for name in (
"costmap-cell-centers-xy-m.npy",
"costmap-cell-indices-xy.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
):
payload = f"sealed:{name}\n".encode()
(source / name).write_bytes(payload)
files[name] = {
"bytes": len(payload),
"sha256": hashlib.sha256(payload).hexdigest(),
}
worker = {
"schema_version": "missioncore.m49-tgs-full-shadow-result/v1",
"status": "passed",
"source_pack_sha256": "a" * 64,
"input_manifest_sha256": "b" * 64,
"config_sha256": "c" * 64,
"timeline": {
"frame_count": 4489,
"available_lidar_frame_count": 3928,
"missing_lidar_frame_count": 561,
},
"point_accounting": {"unaccounted": 0},
"costmap": {"cell_size_m": 0.45, "radius_m": 12.0},
"performance": {},
"acceptance": {},
"files": files,
}
(source / "result.json").write_text(json.dumps(worker), encoding="utf-8")
(source / "worker-summary.json").write_text(json.dumps({
"schema_version": "missioncore.m49-tgs-full-shadow-worker-summary/v1",
"gpu_requested": False,
"aos_used": False,
"all_timeline_frames_accounted": True,
"all_eligible_points_accounted": True,
"canonical_triton_health": "healthy",
"canonical_triton_id": "triton",
"wall_seconds": 1.0,
}), encoding="utf-8")
profile = tmp_path / "profile.json"
profile.write_text(json.dumps({
"schema_version": "missioncore.m49-tgs-full-shadow-profile/v1",
"profile_id": "test",
"profile": {"history_seconds": 1.0},
"costmap": {"state_priority": ["NONGROUND_OCCUPIED"]},
"state_codes": {"UNOBSERVED": 0},
}), encoding="utf-8")
visual = "m4-threat-replay-" + "d" * 64
semantic = "e47-semantic-slam-" + "e" * 64
sealed = seal_m49_tgs_full_shadow(
source_root=source,
destination_root=tmp_path / "results",
profile_path=profile,
linked_visual_result_id=visual,
linked_semantic_result_id=semantic,
created_at_utc="2026-08-26T20:27:19Z",
)
assert sealed.report["source"]["linked_visual_result_id"] == visual
assert sealed.report["source"]["linked_semantic_result_id"] == semantic
assert sealed.manifest["identity"]["linked_semantic_result_id"] == semantic
def test_full_shadow_chunk_contract_keeps_missing_lidar_unobserved(
monkeypatch,
tmp_path: Path,
) -> None:
frames = tuple(
{
"source_frame_index": index,
"session_seconds": index / 10,
"sample_available": index != 1,
"eligible_point_count": 0 if index == 1 else 1,
"ground_point_count": 0,
"nonground_point_count": 0 if index == 1 else 1,
"rejected_point_count": 0,
"occupied_cell_count": 0 if index == 1 else 1,
}
for index in range(4489)
)
centers = np.zeros((2244, 2), dtype=np.float32)
states = np.broadcast_to(np.zeros((1, 2244), dtype=np.uint8), (4489, 2244))
z_bounds = np.broadcast_to(
np.full((1, 2244, 2), np.nan, dtype=np.float32),
(4489, 2244, 2),
)
monkeypatch.setattr(full_shadow_api, "_frames", lambda *_args: frames)
monkeypatch.setattr(
full_shadow_api.np,
"load",
lambda path, **_kwargs: (
centers if "centers" in str(path) else states if "states" in str(path) else z_bounds
),
)
content = full_shadow_api._chunk_json_cached.__wrapped__(
str(tmp_path),
"m49-tgs-full-shadow-" + "a" * 64,
0,
2,
(0, 0),
)
payload = json.loads(content)
assert payload["schema_version"] == "missioncore.m49-tgs-full-shadow-spatial-chunk/v1"
assert payload["count"] == 2
assert len(payload["costmap"]["centers_xy_m"]) == 2244
assert payload["frames"][1]["sample_available"] is False
assert set(payload["frames"][1]["states"]) == {0}