feat(observatory): admit canonical recorded replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 19:21:27 +03:00
parent e6a9846167
commit 023151c186
32 changed files with 4069 additions and 342 deletions
@@ -1026,7 +1026,7 @@ export async function fetchVegetationRouteTgsAnchor(
};
}
export async function fetchVegetationShadowResult(
export async function fetchVegetationShadowResultMetadata(
resultId: string,
{
fetcher = fetch,
@@ -1048,6 +1048,17 @@ export async function fetchVegetationShadowResult(
resultId,
"/api/v1/laboratory/vegetation-shadow",
);
return result;
}
export async function fetchVegetationShadowResult(
resultId: string,
{
fetcher = fetch,
signal,
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<VegetationShadowResult> {
const result = await fetchVegetationShadowResultMetadata(resultId, { fetcher, signal });
if (!result.routeFullReview) return result;
const timelineResponse = await fetcher(
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/route-timeline`,
@@ -0,0 +1,195 @@
export interface ObservationLabReplayCapability {
schemaVersion: "missioncore.observation-lab-replay-capability/v1";
kind: "canonical-recorded-rerun";
viewerProfile: "recorded-session";
timeline: "session_time";
activation: "explicit";
commandsEnabled: false;
}
export class ObservationLabReplayCapabilityContractError extends Error {
constructor(message: string) {
super(message);
this.name = "ObservationLabReplayCapabilityContractError";
}
}
const SHA256 = /^[a-f0-9]{64}$/;
const CAPABILITY_KEYS = new Set([
"schema_version",
"kind",
"viewer_profile",
"timeline",
"activation",
"commands_enabled",
]);
const CANONICAL_PROVENANCE_KEYS = new Set([
"schema_version",
"evidence_identity_sha256",
"result_document_sha256",
"replay_capability",
"authority",
"method",
]);
const AUTHORITY_KEYS = new Set([
"commands_enabled",
"navigation_or_safety_accepted",
"actuation_accepted",
]);
const METHOD_KEYS = new Set([
"schema_version",
"completeness",
"execution_class",
"pipeline_id",
"components",
]);
const METHOD_COMPONENT_KEYS = new Set([
"kind",
"name",
"version",
"role",
"identity_sha256",
]);
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function assertExactKeys(
value: Record<string, unknown>,
expected: ReadonlySet<string>,
label: string,
): void {
const keys = Object.keys(value);
if (keys.length !== expected.size || keys.some((key) => !expected.has(key))) {
throw new ObservationLabReplayCapabilityContractError(
`${label} содержит неизвестные или пропущенные поля.`,
);
}
}
export function decodeLabReplayCapability(
value: unknown,
sessionId: string,
): ObservationLabReplayCapability | null {
if (value === null || value === undefined) return null;
if (!isRecord(value)) {
throw new ObservationLabReplayCapabilityContractError(
`Replay-возможность LAB-сессии ${sessionId} должна быть объектом или null.`,
);
}
assertExactKeys(value, CAPABILITY_KEYS, `Replay-возможность LAB-сессии ${sessionId}`);
if (
value.schema_version !== "missioncore.observation-lab-replay-capability/v1"
|| value.kind !== "canonical-recorded-rerun"
|| value.viewer_profile !== "recorded-session"
|| value.timeline !== "session_time"
|| value.activation !== "explicit"
|| value.commands_enabled !== false
) {
throw new ObservationLabReplayCapabilityContractError(
`Replay-возможность LAB-сессии ${sessionId} нарушает observation-only контракт.`,
);
}
return {
schemaVersion: "missioncore.observation-lab-replay-capability/v1",
kind: "canonical-recorded-rerun",
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
commandsEnabled: false,
};
}
export function decodeCanonicalLabReplayCapabilityProvenance(
provenance: Readonly<Record<string, unknown>>,
sessionId: string,
resultId: string,
): ObservationLabReplayCapability {
assertExactKeys(provenance, CANONICAL_PROVENANCE_KEYS, `Canonical provenance LAB-сессии ${sessionId}`);
if (
provenance.schema_version !== "missioncore.canonical-recorded-lab-projection/v1"
|| typeof provenance.evidence_identity_sha256 !== "string"
|| !SHA256.test(provenance.evidence_identity_sha256)
|| resultId !== `lab-v1-vegetation-shadow-${provenance.evidence_identity_sha256}`
|| typeof provenance.result_document_sha256 !== "string"
|| !SHA256.test(provenance.result_document_sha256)
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} потеряла immutable identity.`,
);
}
if (!isRecord(provenance.authority)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит authority.`,
);
}
assertExactKeys(provenance.authority, AUTHORITY_KEYS, `Canonical authority LAB-сессии ${sessionId}`);
if (
provenance.authority.commands_enabled !== false
|| provenance.authority.navigation_or_safety_accepted !== false
|| provenance.authority.actuation_accepted !== false
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} нарушает observation-only authority.`,
);
}
if (!isRecord(provenance.method)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит method manifest.`,
);
}
assertExactKeys(provenance.method, METHOD_KEYS, `Canonical method LAB-сессии ${sessionId}`);
if (
provenance.method.schema_version !== "missioncore.laboratory-method/v1"
|| provenance.method.completeness !== "legacy-partial"
|| provenance.method.execution_class !== "ai-inference"
|| provenance.method.pipeline_id
!== "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1"
|| !Array.isArray(provenance.method.components)
|| provenance.method.components.length !== 1
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical method LAB-сессии ${sessionId} не соответствует записанному прогону.`,
);
}
const component = provenance.method.components[0];
if (!isRecord(component)) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical method LAB-сессии ${sessionId} не содержит source component.`,
);
}
assertExactKeys(
component,
METHOD_COMPONENT_KEYS,
`Canonical source component LAB-сессии ${sessionId}`,
);
if (
component.kind !== "source"
|| component.name !== "sealed full-route LAB result"
|| component.version !== "missioncore.lab-v1-vegetation-shadow/v1"
|| component.role !== "immutable Session catalog projection"
|| component.identity_sha256 !== provenance.evidence_identity_sha256
) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical source component LAB-сессии ${sessionId} потерял immutable identity.`,
);
}
const capability = decodeLabReplayCapability(provenance.replay_capability, sessionId);
if (capability === null) {
throw new ObservationLabReplayCapabilityContractError(
`Canonical provenance LAB-сессии ${sessionId} не содержит replay capability.`,
);
}
return capability;
}
/** Rolling bridge for a new frontend against the pre-v2 catalog endpoint. */
export function decodeRollingCanonicalLabReplayCapability(
provenance: Readonly<Record<string, unknown>>,
sessionId: string,
resultId: string,
): ObservationLabReplayCapability | null {
if (!Object.prototype.hasOwnProperty.call(provenance, "replay_capability")) return null;
return decodeCanonicalLabReplayCapabilityProvenance(provenance, sessionId, resultId);
}
@@ -0,0 +1,43 @@
export interface ObservationReplayAttempt {
readonly signal: AbortSignal;
isCurrent: () => boolean;
finish: () => boolean;
}
export interface ObservationReplayCoordinator {
begin: () => ObservationReplayAttempt;
cancel: () => void;
}
/** Latest explicit admission owns the viewer, even if an older promise settles later. */
export function createObservationReplayCoordinator(): ObservationReplayCoordinator {
let sequence = 0;
let active: AbortController | null = null;
return {
begin() {
active?.abort();
const controller = new AbortController();
const attemptSequence = ++sequence;
active = controller;
return {
signal: controller.signal,
isCurrent: () => (
!controller.signal.aborted
&& active === controller
&& sequence === attemptSequence
),
finish: () => {
if (active !== controller || sequence !== attemptSequence) return false;
active = null;
return true;
},
};
},
cancel() {
active?.abort();
// Keep settlement ownership until finish(). isCurrent() already fails,
// while begin() can replace this attempt immediately.
},
};
}
@@ -1,6 +1,14 @@
import {
MAX_RECORDED_CAMERA_SOURCES,
} from "./recordedSessionAdmission";
import {
decodeLabReplayCapability,
decodeRollingCanonicalLabReplayCapability,
ObservationLabReplayCapabilityContractError,
type ObservationLabReplayCapability,
} from "./labReplayCapability";
export type { ObservationLabReplayCapability } from "./labReplayCapability";
export type ObservationSessionStatus =
| "recording"
@@ -20,6 +28,7 @@ export interface ObservationLabInstance {
configSha256: string | null;
runCreatedAtUtc: string;
publishedAtUtc: string;
replayCapability: ObservationLabReplayCapability | null;
provenance: Readonly<Record<string, unknown>>;
}
@@ -155,7 +164,7 @@ const ITEM_KEYS = new Set([
"preparation",
"lab",
]);
const LAB_KEYS = new Set([
const LEGACY_LAB_KEYS = new Set([
"lab_id",
"source_session_id",
"result_kind",
@@ -166,6 +175,7 @@ const LAB_KEYS = new Set([
"published_at_utc",
"provenance",
]);
const LAB_KEYS = new Set([...LEGACY_LAB_KEYS, "replay_capability"]);
const CATALOG_PREPARATION_KEYS = new Set([
"preparation_id",
"state",
@@ -394,7 +404,15 @@ function decodeLabInstance(
`LAB-привязка сессии ${sessionId} должна быть объектом или null.`,
);
}
assertExactKeys(value, LAB_KEYS, `LAB-привязка сессии ${sessionId}`);
const hasTypedCapability = Object.prototype.hasOwnProperty.call(
value,
"replay_capability",
);
assertExactKeys(
value,
hasTypedCapability ? LAB_KEYS : LEGACY_LAB_KEYS,
`LAB-привязка сессии ${sessionId}`,
);
const labId = requireString(value.lab_id, `lab(${sessionId}).lab_id`, 36);
if (!/^LAB [A-Z][A-Z0-9._-]{0,31}$/.test(labId)) {
throw new ObservationSessionContractError("Каталог содержит некорректный LAB-маркер.");
@@ -430,6 +448,17 @@ function decodeLabInstance(
if (!isRecord(value.provenance)) {
throw new ObservationSessionContractError("LAB provenance должен быть JSON-объектом.");
}
let replayCapability: ObservationLabReplayCapability | null;
try {
replayCapability = hasTypedCapability
? decodeLabReplayCapability(value.replay_capability, sessionId)
: decodeRollingCanonicalLabReplayCapability(value.provenance, sessionId, resultId);
} catch (error) {
if (error instanceof ObservationLabReplayCapabilityContractError) {
throw new ObservationSessionContractError(error.message);
}
throw error;
}
return {
labId,
sourceSessionId,
@@ -445,6 +474,7 @@ function decodeLabInstance(
value.published_at_utc,
`lab(${sessionId}).published_at_utc`,
),
replayCapability,
provenance: value.provenance,
};
}
@@ -1181,6 +1211,7 @@ export async function fetchObservationSessionCatalog({
queryParameters.set("limit", String(Number(limit)));
}
if (scope !== "all") queryParameters.set("scope", scope);
if (scope === "laboratory") queryParameters.set("lab_contract", "v2");
const serializedQuery = queryParameters.toString();
const query = serializedQuery ? `?${serializedQuery}` : "";
let response: Response;
@@ -12,6 +12,16 @@ import {
type ObservationSessionScope,
type ObservationSessionSummary,
} from "./sessionArchive";
import {
createObservationReplayCoordinator,
type ObservationReplayCoordinator,
} from "./replayCoordinator";
export {
createObservationReplayCoordinator,
type ObservationReplayAttempt,
type ObservationReplayCoordinator,
} from "./replayCoordinator";
export type ObservationSessionsLoadState = "idle" | "loading" | "ready" | "error";
export type ObservationReplayOutcome = "accepted" | "error" | "cancelled";
@@ -41,17 +51,6 @@ export interface ObservationSessionsController {
remove: (sessionId: string) => Promise<boolean>;
}
export interface ObservationReplayAttempt {
readonly signal: AbortSignal;
isCurrent: () => boolean;
finish: () => boolean;
}
export interface ObservationReplayCoordinator {
begin: () => ObservationReplayAttempt;
cancel: () => void;
}
export async function deleteObservationSessionAfterTeardown(
sessionId: string,
{
@@ -93,41 +92,6 @@ export class ObservationPreparationStalledError extends Error {
}
}
/** Latest selection wins, even if an obsolete server job finishes later. */
export function createObservationReplayCoordinator(): ObservationReplayCoordinator {
let sequence = 0;
let active: AbortController | null = null;
return {
begin() {
active?.abort();
const controller = new AbortController();
const attemptSequence = ++sequence;
active = controller;
return {
signal: controller.signal,
isCurrent: () => (
!controller.signal.aborted &&
active === controller &&
sequence === attemptSequence
),
finish: () => {
if (active !== controller || sequence !== attemptSequence) return false;
active = null;
return true;
},
};
},
cancel() {
active?.abort();
// Keep ownership until the cancelled attempt reaches `finish()`. This
// lets its finally block settle a replacement that already passed
// onReplayBegin, while `isCurrent()` still fails immediately because the
// signal is aborted. A later `begin()` replaces and invalidates it.
},
};
}
function errorMessage(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
@@ -4,6 +4,10 @@ import {
type ObservationSessionFetch,
type ObservationSessionSummary,
} from "../observation/sessionArchive";
import {
observatoryRecordedRunBinding,
type ObservatoryRecordedRunBinding,
} from "./recordedRun";
export interface ObservatoryEvidence {
readonly sessionId: string;
@@ -11,6 +15,7 @@ export interface ObservatoryEvidence {
readonly status: ObservationSessionSummary["status"];
readonly publishedAtUtc: string;
readonly lab: ObservationLabInstance;
readonly recordedRun: ObservatoryRecordedRunBinding | null;
}
export interface ObservatorySession {
@@ -61,6 +66,7 @@ function evidenceFromSession(
status: session.status,
publishedAtUtc: session.lab.publishedAtUtc,
lab: session.lab,
recordedRun: observatoryRecordedRunBinding(session.id, session.lab),
};
}
@@ -0,0 +1,129 @@
import type { LaboratoryFetch } from "../laboratory/advancedResults";
import {
fetchVegetationShadowResultMetadata,
type VegetationFullRouteReview,
type VegetationShadowResult,
} from "../laboratory/vegetationShadow";
import type { ObservationLabInstance } from "../observation/sessionArchive";
import {
decodeCanonicalLabReplayCapabilityProvenance,
ObservationLabReplayCapabilityContractError,
} from "../observation/labReplayCapability";
const CANONICAL_RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
const CANONICAL_SOURCE_SESSION_ID = "20260828T130511Z_viewer_live";
export interface ObservatoryRecordedRunBinding {
readonly kind: "canonical-recorded-rerun";
readonly evidenceSessionId: string;
readonly sourceSessionId: string;
readonly resultId: string;
readonly viewerProfile: "recorded-session";
readonly timeline: "session_time";
readonly activation: "explicit";
}
export class ObservatoryRecordedRunContractError extends Error {
constructor(message: string) {
super(message);
this.name = "ObservatoryRecordedRunContractError";
}
}
export function observatoryRecordedRunBinding(
evidenceSessionId: string,
lab: ObservationLabInstance,
): ObservatoryRecordedRunBinding | null {
const capability = lab.replayCapability;
if (!capability) return null;
try {
decodeCanonicalLabReplayCapabilityProvenance(
lab.provenance,
evidenceSessionId,
lab.resultId,
);
} catch (error) {
if (!(error instanceof ObservationLabReplayCapabilityContractError)) throw error;
throw new ObservatoryRecordedRunContractError(error.message);
}
if (
evidenceSessionId !== lab.resultId
|| lab.sourceSessionId !== CANONICAL_SOURCE_SESSION_ID
|| lab.labId !== "LAB V1"
|| lab.resultKind !== "recorded-perception-qualification"
|| !CANONICAL_RESULT_ID.test(lab.resultId)
|| lab.configSha256 !== null
|| lab.sourceResultId === null
|| !CANONICAL_RESULT_ID.test(lab.sourceResultId)
|| capability.kind !== "canonical-recorded-rerun"
|| capability.viewerProfile !== "recorded-session"
|| capability.timeline !== "session_time"
|| capability.activation !== "explicit"
|| capability.commandsEnabled !== false
) {
throw new ObservatoryRecordedRunContractError(
`LAB-результат ${lab.resultId} не допущен к каноническому recorded replay.`,
);
}
return {
kind: "canonical-recorded-rerun",
evidenceSessionId,
sourceSessionId: lab.sourceSessionId,
resultId: lab.resultId,
viewerProfile: "recorded-session",
timeline: "session_time",
activation: "explicit",
};
}
export async function fetchObservatoryRecordedRunReview(
binding: ObservatoryRecordedRunBinding,
{
selectedSourceSessionId,
fetcher = fetch,
signal,
}: {
selectedSourceSessionId: string;
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
},
): Promise<VegetationFullRouteReview> {
if (binding.sourceSessionId !== selectedSourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запуск не связан с выбранной исходной сессией.",
);
}
const result = await fetchVegetationShadowResultMetadata(binding.resultId, {
fetcher,
signal,
});
return admitObservatoryRecordedRunReview(
binding,
selectedSourceSessionId,
result,
);
}
export function admitObservatoryRecordedRunReview(
binding: ObservatoryRecordedRunBinding,
selectedSourceSessionId: string,
result: VegetationShadowResult,
): VegetationFullRouteReview {
if (binding.sourceSessionId !== selectedSourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запуск не связан с выбранной исходной сессией.",
);
}
if (result.resultId !== binding.resultId) {
throw new ObservatoryRecordedRunContractError(
"Запечатанный результат не совпадает с выбранным запуском.",
);
}
const review = result.routeFullReview;
if (!review || review.sessionId !== binding.sourceSessionId) {
throw new ObservatoryRecordedRunContractError(
"Запечатанный результат потерял точную связь с исходной сессией.",
);
}
return review;
}