fix(observatory): fence portable catalog refresh
This commit is contained in:
@@ -41,10 +41,11 @@ function decodePortableSetup(value: unknown): ObservatoryLaboratorySetup {
|
||||
decodePortableSourceRequirements(row.source_requirements);
|
||||
|
||||
const compatibility = record(row.source_compatibility, "portable source_compatibility");
|
||||
const compatibilityKeys = compatibility.evidence === undefined
|
||||
? ["compatible", "outcome", "reason"]
|
||||
: ["compatible", "evidence", "outcome", "reason"];
|
||||
exactKeys(compatibility, compatibilityKeys, "portable source_compatibility");
|
||||
exactKeys(
|
||||
compatibility,
|
||||
["compatible", "outcome", "reason"],
|
||||
"portable source_compatibility",
|
||||
);
|
||||
const compatible = boolean(compatibility.compatible, "portable compatible");
|
||||
exact(
|
||||
compatibility.outcome,
|
||||
@@ -52,15 +53,6 @@ function decodePortableSetup(value: unknown): ObservatoryLaboratorySetup {
|
||||
"portable compatibility outcome",
|
||||
);
|
||||
const compatibilityReason = text(compatibility.reason, "portable compatibility reason");
|
||||
if (compatibility.evidence !== undefined) {
|
||||
const evidence = record(compatibility.evidence, "portable compatibility evidence");
|
||||
exactKeys(evidence, [
|
||||
"frame_count", "timeline_end_seconds", "timeline_start_seconds",
|
||||
], "portable compatibility evidence");
|
||||
positiveInteger(evidence.frame_count, "portable frame_count");
|
||||
finiteNumber(evidence.timeline_start_seconds, "portable timeline_start_seconds");
|
||||
finiteNumber(evidence.timeline_end_seconds, "portable timeline_end_seconds");
|
||||
}
|
||||
|
||||
const executor = record(row.executor, "portable executor");
|
||||
exactKeys(executor, ["contour_id", "ready", "reason", "state"], "portable executor");
|
||||
@@ -258,15 +250,6 @@ function positiveInteger(value: unknown, label: string): number {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
function finiteNumber(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new ObservatoryPortableSetupDecodeError(
|
||||
`${label}: ожидалось конечное число.`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exact<T>(value: unknown, expected: T, label: string): T {
|
||||
if (value !== expected) {
|
||||
throw new ObservatoryPortableSetupDecodeError(
|
||||
|
||||
@@ -52,25 +52,37 @@ export function useObservatoryLaboratorySetups(sourceSessionId: string) {
|
||||
void fetchObservatoryLaboratorySetups(sourceSessionId, { signal: request.signal })
|
||||
.then(async (legacyCatalog) => {
|
||||
if (request.signal.aborted || requestSequence.current !== sequence) return;
|
||||
publishSetupCatalog(legacyCatalog, setCatalog, setSelectedSetupId);
|
||||
publishSetupCatalog(
|
||||
legacyCatalog,
|
||||
setCatalog,
|
||||
setSelectedSetupId,
|
||||
{ preserveUnknownSelection: true },
|
||||
);
|
||||
setState("ready");
|
||||
const optionalPortable = await portableResult;
|
||||
if (request.signal.aborted || requestSequence.current !== sequence) return;
|
||||
if (optionalPortable.status === "fulfilled") {
|
||||
publishSetupCatalog(
|
||||
mergeSetupCatalogs(legacyCatalog, optionalPortable.value),
|
||||
setCatalog,
|
||||
setSelectedSetupId,
|
||||
);
|
||||
setError(null);
|
||||
try {
|
||||
publishSetupCatalog(
|
||||
mergeSetupCatalogs(legacyCatalog, optionalPortable.value),
|
||||
setCatalog,
|
||||
setSelectedSetupId,
|
||||
);
|
||||
setError(null);
|
||||
} catch (caught) {
|
||||
publishSetupCatalog(legacyCatalog, setCatalog, setSelectedSetupId);
|
||||
setError(catalogErrorMessage(
|
||||
caught,
|
||||
"Portable-каталог LAB V1 нарушил локальный контракт.",
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
setError(
|
||||
optionalPortable.reason instanceof Error
|
||||
&& optionalPortable.reason.message.trim()
|
||||
? optionalPortable.reason.message
|
||||
: "Portable-каталог LAB V1 недоступен.",
|
||||
);
|
||||
publishSetupCatalog(legacyCatalog, setCatalog, setSelectedSetupId);
|
||||
setError(catalogErrorMessage(
|
||||
optionalPortable.reason,
|
||||
"Portable-каталог LAB V1 недоступен.",
|
||||
));
|
||||
})
|
||||
.catch((caught: unknown) => {
|
||||
if (request.signal.aborted || requestSequence.current !== sequence) return;
|
||||
@@ -84,6 +96,12 @@ export function useObservatoryLaboratorySetups(sourceSessionId: string) {
|
||||
|
||||
useEffect(() => () => preflightRequest.current?.abort(), []);
|
||||
|
||||
useEffect(() => {
|
||||
preflightRequest.current?.abort();
|
||||
preflightRequest.current = null;
|
||||
setPreflight((current) => current.kind === "idle" ? current : { kind: "idle" });
|
||||
}, [selectedSetupId, sourceSessionId]);
|
||||
|
||||
const selectedSetup = useMemo(
|
||||
() => activeCatalog?.setups.find((setup) => setup.setupId === selectedSetupId) ?? null,
|
||||
[activeCatalog, selectedSetupId],
|
||||
@@ -147,12 +165,14 @@ function publishSetupCatalog(
|
||||
next: ObservatoryLaboratorySetupCatalog,
|
||||
setCatalog: (catalog: ObservatoryLaboratorySetupCatalog) => void,
|
||||
setSelectedSetupId: (update: (current: string) => string) => void,
|
||||
{ preserveUnknownSelection = false }: { preserveUnknownSelection?: boolean } = {},
|
||||
): void {
|
||||
setCatalog(next);
|
||||
setSelectedSetupId((current) => {
|
||||
if (next.setups.some(
|
||||
(setup) => setup.setupId === current && setup.compatibility.compatible,
|
||||
)) return current;
|
||||
if (preserveUnknownSelection && current) return current;
|
||||
return next.setups.find(
|
||||
(setup) => setup.compatibility.compatible && setup.preflight.outcome === "existing",
|
||||
)?.setupId
|
||||
@@ -162,6 +182,12 @@ function publishSetupCatalog(
|
||||
});
|
||||
}
|
||||
|
||||
function catalogErrorMessage(caught: unknown, fallback: string): string {
|
||||
return caught instanceof Error && caught.message.trim()
|
||||
? caught.message
|
||||
: fallback;
|
||||
}
|
||||
|
||||
function mergeSetupCatalogs(
|
||||
legacy: ObservatoryLaboratorySetupCatalog,
|
||||
portable: ObservatoryLaboratorySetupCatalog,
|
||||
|
||||
@@ -220,9 +220,16 @@ export function ObservatoryWorkspace({
|
||||
: "Несовместим с выбранной сессией",
|
||||
})) ?? []
|
||||
), [setupController.catalog]);
|
||||
const runPreflight = setupController.preflight.kind === "ready"
|
||||
const preflightCandidate = setupController.preflight.kind === "ready"
|
||||
? setupController.preflight.value
|
||||
: null;
|
||||
const runPreflight = preflightCandidate
|
||||
&& preflightCandidate.sourceSessionId === selectedSession?.source.id
|
||||
&& preflightCandidate.setupId === setupController.selectedSetup?.setupId
|
||||
&& preflightCandidate.definitionSha256
|
||||
=== setupController.selectedSetup?.runDefinition?.definitionSha256
|
||||
? preflightCandidate
|
||||
: null;
|
||||
const queueSubmissionAllowed = Boolean(
|
||||
setupController.selectedSetup?.runDefinition
|
||||
&& setupController.selectedSetup.compatibility.compatible
|
||||
|
||||
Reference in New Issue
Block a user