fix(observatory): offer only uncalculated profiles without completion badges

This commit is contained in:
DCCONSTRUCTIONS
2026-09-03 10:16:01 +03:00
parent c2710b55a7
commit e436fb56d4
14 changed files with 420 additions and 368 deletions
@@ -0,0 +1,96 @@
import assert from "node:assert/strict";
import React from "react";
import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let useObservatoryRecordedJobs;
let useObservatoryLaboratorySetups;
before(async () => {
server = await createServer({
appType: "custom", logLevel: "silent", server: { middlewareMode: true },
});
({ useObservatoryRecordedJobs } = await server.ssrLoadModule("/src/core/observatory/useObservatoryRecordedJobs.ts"));
({ useObservatoryLaboratorySetups } = await server.ssrLoadModule("/src/core/observatory/useObservatoryLaboratorySetups.ts"));
});
after(async () => { await server?.close(); });
// Deliberately inspect the render BEFORE effects/cleanup: stale state must already be hidden.
function renderBeforeEffects(hook, args, stateSlots) {
const internals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
const previous = internals.H;
let stateIndex = 0;
internals.H = {
useState: (initial) => [
stateIndex < stateSlots.length ? stateSlots[stateIndex++]
: typeof initial === "function" ? initial() : initial,
() => {},
],
useRef: (value) => ({ current: value }),
useMemo: (factory) => factory(),
useCallback: (fn) => fn,
useEffect: () => {},
};
try { return hook(...args); } finally { internals.H = previous; }
}
test("queue data and errors cannot survive a source/setup/version change for one render", () => {
const original = ["source-a", "profile-a", "a".repeat(64)];
const activeJob = { state: "running", publication: { state: "not-required" } };
const snapshot = {
selectionKey: JSON.stringify(original), jobs: [activeJob], state: "ready", error: "old error",
};
assert.equal(renderBeforeEffects(useObservatoryRecordedJobs, original, [snapshot]).activeJob, activeJob);
for (const next of [
["source-b", original[1], original[2]],
[original[0], "profile-b", original[2]],
[original[0], original[1], "b".repeat(64)],
["", "", ""],
]) {
const view = renderBeforeEffects(useObservatoryRecordedJobs, next, [snapshot]);
assert.deepEqual(view.jobs, []);
assert.equal(view.activeJob, null);
assert.equal(view.latestJob, null);
assert.equal(view.error, null);
assert.notEqual(view.state, "ready");
}
});
test("published, incompatible and previous-source profiles have no effective selection", () => {
const profile = {
setupId: "profile-a", origin: "portable-definition",
compatibility: { compatible: true },
runDefinition: { definitionSha256: "a".repeat(64) },
preflight: { outcome: "ready" },
};
function render(sourceId, setups) {
return renderBeforeEffects(useObservatoryLaboratorySetups, [sourceId], [
{ sourceSessionId: "source-a", setups }, "ready", null, "profile-a", 0, { kind: "idle" },
]);
}
assert.equal(render("source-a", [profile]).selectedSetupId, "profile-a");
for (const view of [
render("source-b", [profile]),
render("source-a", [{ ...profile, preflight: { outcome: "existing" } }]),
render("source-a", [{ ...profile, compatibility: { compatible: false } }]),
render("source-a", []),
]) {
assert.equal(view.selectedSetupId, "");
assert.equal(view.selectedSetup, null);
assert.deepEqual(view.selectableSetups, []);
}
});
test("historical failures are not presented as errors of a new operator action", () => {
const selection = ["source-a", "profile-a", "a".repeat(64)];
const snapshot = {
selectionKey: JSON.stringify(selection),
jobs: [{ jobId: "old-run", state: "failed", publication: { state: "not-required" } }],
state: "ready", error: null,
};
const view = renderBeforeEffects(useObservatoryRecordedJobs, selection, [snapshot]);
assert.equal(view.computationFailed, false);
assert.equal(view.error, null);
assert.equal(view.activeJob, null);
});
@@ -7,6 +7,7 @@ let server;
let fetchObservatoryLaboratorySetups;
let fetchObservatoryPortableLaboratorySetups;
let preflightObservatoryLaboratorySetup;
let selectableObservatorySetups;
let ObservatoryLaboratorySetupContractError;
const authority = {
@@ -168,6 +169,7 @@ before(async () => {
fetchObservatoryLaboratorySetups,
fetchObservatoryPortableLaboratorySetups,
preflightObservatoryLaboratorySetup,
selectableObservatorySetups,
ObservatoryLaboratorySetupContractError,
} = await server.ssrLoadModule("/src/core/observatory/laboratorySetups.ts"));
});
@@ -396,6 +398,38 @@ test("portable exact cached result is readable without an installed executor", a
assert.equal(setup.preservedResults[0].access, "observatory");
});
test("selector contains only compatible uncalculated portable definitions", async () => {
const uncached = (await fetchCachedPortable(portableSetup())).setups[0];
const cached = (await fetchCachedPortable(cachedPortableSetup())).setups[0];
const currentVersion = {
...uncached,
runDefinition: { ...uncached.runDefinition, version: 2, definitionSha256: "f".repeat(64) },
};
const catalog = (setups) => ({ sourceSessionId: "source-a", setups });
assert.deepEqual(selectableObservatorySetups(null), []);
assert.deepEqual(selectableObservatorySetups(catalog([])), []);
assert.deepEqual(selectableObservatorySetups(catalog([cached])), []);
// Same display label and setup ID, but no verified cache hit for the new definition.
assert.deepEqual(selectableObservatorySetups(catalog([currentVersion])), [currentVersion]);
assert.deepEqual(selectableObservatorySetups(catalog([
cached,
{ ...uncached, setupId: "remaining-profile" },
])).map((s) => s.setupId), ["remaining-profile"]);
assert.deepEqual(selectableObservatorySetups(catalog([
{ ...uncached, compatibility: { compatible: false, reasons: [] } },
{ ...uncached, origin: "archived-definition" },
{ ...uncached, origin: "existing-result" },
{ ...uncached, runDefinition: null },
])), []);
// Worker availability does not masquerade as completed computation.
assert.equal(uncached.executor.state, "not-installed");
assert.deepEqual(selectableObservatorySetups(catalog([uncached])), [uncached]);
// A missing/deleted/unpublished projection is not a completed result.
assert.deepEqual(selectableObservatorySetups(catalog([{
...uncached, preservedResults: cached.preservedResults,
}])).map((s) => s.setupId), [uncached.setupId]);
});
for (const [label, change] of [
["another source", (s) => { s.existing_results[0].identity.source_session_id = "source-b"; }],
["another setup", (s) => { s.existing_results[0].identity.setup_id = "another-profile"; }],
@@ -245,3 +245,33 @@ test("publication retry never submits a second compute request", async () => {
assert.equal(request.init.body, undefined);
assert.equal(retried.publication.state, "published");
});
test("queue query and response are fenced to the selected definition", async () => {
const oldVersion = job("succeeded");
oldVersion.setup.definition_sha256 = "f".repeat(64);
const otherSource = job("running");
otherSource.source.session_id = "source-b";
const otherSetup = job("running");
otherSetup.setup.setup_id = "other-setup";
let url;
const jobs = await fetchObservatoryRecordedJobs("source-a", "m49-tgs", {
definitionSha256: "8".repeat(64),
fetcher: async (input) => {
url = new URL(String(input), "http://localhost");
return new Response(JSON.stringify({
schema_version: "missioncore.observatory-recorded-job-list/v1",
items: [oldVersion, otherSource, otherSetup, job("queued")], authority,
}));
},
});
assert.equal(url.searchParams.get("definition_sha256"), "8".repeat(64));
assert.deepEqual(jobs.map((j) => j.state), ["queued"]);
});
test("portable submit rejects a successful response for an old definition", async () => {
await assert.rejects(submitObservatoryRecordedJob(
"source-a", "m49-tgs", "test-request",
{ definitionSha256: "f".repeat(64), checkSha256: "e".repeat(64) },
{ fetcher: async () => new Response(JSON.stringify(job("succeeded"))) },
), ObservatoryRecordedJobContractError);
});
@@ -75,8 +75,8 @@ test("Observatory mounts the one shared canonical replay only after explicit adm
assert.match(workspace, /useObservatoryCatalog/);
assert.match(workspace, /Связанных результатов нет/);
assert.match(workspace, /не является выводом о качестве/);
assert.match(workspace, /\.evidence\.slice\([\s\S]*MAX_PRESENTED_EVIDENCE/);
assert.match(workspace, /Полный архив остаётся в legacy LAB/);
assert.match(workspace, /presentedEvidence = selectedSession\?\.evidence \?\? \[\]/);
assert.doesNotMatch(workspace, /MAX_PRESENTED_EVIDENCE|\.evidence\.slice\(/);
assert.match(workspace, /Полнота исторических/);
assert.match(workspace, /вне текущего загруженного среза/);
assert.match(workspace, /observatory-notice__copy/);
@@ -136,7 +136,7 @@ test("Observatory mounts the one shared canonical replay only after explicit adm
assert.match(viewerProfiles, /kind: "lab-recorded-evidence"/);
assert.match(
styles,
/@media \(max-width: 920px\)[\s\S]*\.observatory-catalog-bar__controls \.nodedc-select-anchor \{[\s\S]*flex: 0 0 auto;/,
/@container observatory-workspace \(max-width: 920px\)[\s\S]*\.observatory-catalog-bar__controls \.nodedc-select-anchor \{[\s\S]*flex: 0 0 auto;/,
);
assert.match(
styles,
@@ -229,7 +229,7 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
assert.match(workspace, /className="observatory-catalog-bar" padding="sm"/);
assert.match(workspace, /label="Выбрать сохранённую сессию"/);
assert.match(workspace, /label="Выбрать сетап лаборатории"/);
assert.match(workspace, /Показан последний каталог сетапов/);
assert.match(workspace, /setupController\.error \? \(/);
assert.doesNotMatch(workspace, /Выбор меняет только читаемую карточку/);
assert.doesNotMatch(workspace, /ObservatorySetupDetail|observatory-setup-detail/);
assert.doesNotMatch(styles, /observatory-setup-detail|observatory-setup-results/);
@@ -240,44 +240,31 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
);
assert.match(
workspace,
/canSubmitRecordedJob \? \([\s\S]*>\s*Рассчитать\s*<\/Button>/,
/showCalculate \? \([\s\S]*>\s*Рассчитать\s*<\/Button>/,
);
assert.match(workspace, /accepted: \{ label: "Принят"/);
assert.match(workspace, /queued: \{ label: "Ждёт Worker"/);
assert.match(workspace, /claimed: \{ label: "Назначен Worker"/);
assert.match(workspace, /running: \{ label: "Выполняется"/);
assert.match(workspace, /paused: \{ label: "Пауза: live-поток"/);
assert.match(
workspace,
/"preemption-pending": \{[\s\S]*label: "Ждём подтверждения остановки"/,
);
assert.match(setupHook, /Promise\.all\(\[legacyResult, portableResult\]\)/);
assert.match(
setupHook,
/legacy\.status === "rejected"[\s\S]*portable\.status === "fulfilled"[\s\S]*publishSetupCatalog\(portable\.value/,
);
assert.match(
setupHook,
/legacy\.status === "fulfilled"[\s\S]*portable\.status === "rejected"[\s\S]*publishSetupCatalog\(legacy\.value/,
);
assert.match(
setupHook,
/portableSetupIds[\s\S]*legacy\.setups\.filter\([\s\S]*!portableSetupIds\.has\(setup\.setupId\)[\s\S]*\.\.\.portable\.setups/,
);
assert.match(setupHook, /selectedSetupId, sourceSessionId/);
assert.match(workspace, /Worker готов к проверке/);
assert.doesNotMatch(workspace, /recordedJobStatus|presentedJobStatus|Расчёт завершён|Результат опубликован|Вычислено ·/);
assert.match(workspace, /setupController\.selectableSetups\.map/);
assert.match(workspace, /showCalculate = setupController\.selectedSetup !== null/);
assert.match(workspace, /disabled=\{!canSubmitRecordedJob\}/);
assert.match(workspace, /aria-busy=\{calculationPending\}/);
const runBar = workspace.slice(workspace.indexOf('<div className="observatory-catalog-bar__run"'), workspace.indexOf("{queueStatusError ?"));
assert.doesNotMatch(runBar, /StatusBadge/);
assert.match(setupHook, /fetchObservatoryPortableLaboratorySetups/);
assert.doesNotMatch(setupHook, /fetchObservatoryLaboratorySetups|mergeSetupCatalogs|legacyResult/);
assert.match(setupHook, /selectableObservatorySetups\(next\)/);
assert.match(setupHook, /selectable\[0\]\?\.setupId \?\? ""/);
assert.match(setupHook, /selectedSetupId, sourceSessionId, selectedDefinitionSha256/);
assert.match(
workspace,
/preflightCandidate\.definitionSha256[\s\S]*selectedSetup\?\.runDefinition\?\.definitionSha256/,
);
assert.match(workspace, /succeeded: \{ label: "Вычислено · ждёт публикации"/);
assert.match(workspace, /failed: \{ label: "Ошибка расчёта"/);
assert.match(workspace, /"reconciliation-required": \{ label: "Нужна сверка"/);
assert.match(
jobsHook,
/OPEN_STATES[\s\S]*"preemption-pending",[\s\S]*"reconciliation-required"/,
);
assert.match(jobsHook, /return `observatory-ui:\$\{entropy\}`;/);
assert.match(jobsHook, /JSON\.stringify\(\[sourceSessionId, setupId, definitionSha256\]\)/);
assert.match(jobsHook, /snapshot\.selectionKey === selectionKey \? snapshot : null/);
assert.doesNotMatch(jobsHook, /observatory-ui:[^`]*sourceSessionId|\.slice\(0, 160\)/);
assert.match(workspace, /recordedJobsController\.refresh\(\)/);
assert.match(jobsHook, /POLL_INTERVAL_MS = 1_500/);
@@ -295,7 +282,7 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
);
assert.match(
styles,
/@media \(max-width: 920px\)[\s\S]*\.observatory-catalog-bar,[\s\S]*\.observatory-catalog-bar__controls,[\s\S]*flex-direction: column;/,
/@container observatory-workspace \(max-width: 920px\)[\s\S]*\.observatory-catalog-bar,[\s\S]*\.observatory-catalog-bar__controls,[\s\S]*flex-direction: column;/,
);
assert.match(setupHook, /catalog\?\.sourceSessionId === sourceSessionId/);
assert.match(setupHook, /preflightRequest\.current\?\.abort\(\)/);