feat(observatory-ui): review portable results and retry publication

Checkpoint the existing generic result-viewing and publication lifecycle UI. Focused architecture and Observatory tests: 62 passed; no new visual changes or rebuild in this checkpoint.
This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 00:59:21 +03:00
parent 62d5520c7a
commit 5a78c997ac
10 changed files with 748 additions and 86 deletions
@@ -5,6 +5,7 @@ import { createServer } from "vite";
let server;
let fetchObservatoryRecordedJobs;
let retryObservatoryRecordedJobPublication;
let submitObservatoryRecordedJob;
let ObservatoryRecordedJobContractError;
@@ -65,6 +66,12 @@ function job(state = "queued") {
result: state === "succeeded"
? { result_id: "m49-result", sha256: "d".repeat(64) }
: null,
publication: {
state: "not-required",
attempts: 0,
error: null,
published_at_utc: null,
},
terminal: state === "failed"
? { code: "worker-failed", message: "Worker завершил расчёт с ошибкой." }
: null,
@@ -82,6 +89,7 @@ before(async () => {
});
({
fetchObservatoryRecordedJobs,
retryObservatoryRecordedJobPublication,
submitObservatoryRecordedJob,
ObservatoryRecordedJobContractError,
} = await server.ssrLoadModule("/src/core/observatory/recordedJobs.ts"));
@@ -212,3 +220,28 @@ test("portable submission carries the exact definition/check fence", async () =>
check_sha256: "f".repeat(64),
});
});
test("publication retry never submits a second compute request", async () => {
let request;
const response = job("succeeded");
response.publication = {
state: "published",
attempts: 2,
error: null,
published_at_utc: "2026-09-01T12:00:00Z",
};
const retried = await retryObservatoryRecordedJobPublication(response.job_id, {
fetcher: async (input, init) => {
request = { input: String(input), init };
return new Response(JSON.stringify(response), { status: 200 });
},
});
assert.equal(
request.input,
`/api/v1/observatory/runs/${response.job_id}/publication/retry`,
);
assert.equal(request.init.method, "POST");
assert.equal(request.init.body, undefined);
assert.equal(retried.publication.state, "published");
});
@@ -6,6 +6,7 @@ import { createServer } from "vite";
let server;
let admitObservatoryRecordedRunReview;
let fetchObservatoryRecordedRunReview;
let observatoryRecordedRunBinding;
let ObservatoryRecordedRunContractError;
@@ -17,6 +18,7 @@ before(async () => {
});
({
admitObservatoryRecordedRunReview,
fetchObservatoryRecordedRunReview,
observatoryRecordedRunBinding,
ObservatoryRecordedRunContractError,
} = await server.ssrLoadModule("/src/core/observatory/recordedRun.ts"));
@@ -87,6 +89,55 @@ function lab(overrides = {}) {
};
}
function portableCapability() {
return {
schemaVersion: "missioncore.observation-lab-replay-capability/v2",
kind: "portable-result-review",
viewerProfile: "portable-result",
timeline: "result-defined",
activation: "explicit",
commandsEnabled: false,
};
}
function portableCapabilityDocument() {
return {
schema_version: "missioncore.observation-lab-replay-capability/v2",
kind: "portable-result-review",
viewer_profile: "portable-result",
timeline: "result-defined",
activation: "explicit",
commands_enabled: false,
};
}
function portableLab(portableResultId, definitionSha256) {
return lab({
labId: "M4.9",
resultId: portableResultId,
resultKind: "recorded-route-analysis",
sourceResultId: null,
configSha256: definitionSha256,
replayCapability: portableCapability(),
provenance: {
schema_version: "missioncore.observatory-portable-result-publication/v1",
authority: {},
calculation_profile: {},
calculation_profile_sha256: "b".repeat(64),
job: {},
source: { session_id: sourceSessionId },
run_definition: { definition_sha256: definitionSha256 },
result_package: {
manifest_sha256: "c".repeat(64),
artifact_manifest_id: "d".repeat(64),
},
replay_capability: portableCapabilityDocument(),
storage: {},
method: {},
},
});
}
test("Observatory admits only the exact typed canonical recorded run", () => {
const binding = observatoryRecordedRunBinding(resultId, lab());
assert.deepEqual(binding, {
@@ -167,6 +218,67 @@ test("Observatory rechecks the selected source and sealed review before mounting
);
});
test("Observatory selects and strictly decodes a portable result viewer by capability", async () => {
const portableResultId = "portable-result-a";
const definitionSha256 = "e".repeat(64);
const binding = observatoryRecordedRunBinding(
portableResultId,
portableLab(portableResultId, definitionSha256),
);
assert.deepEqual(binding, {
kind: "portable-result-review",
evidenceSessionId: portableResultId,
sourceSessionId,
resultId: portableResultId,
viewerProfile: "portable-result",
timeline: "result-defined",
activation: "explicit",
definitionSha256,
});
let requestedUrl = null;
const review = await fetchObservatoryRecordedRunReview(binding, {
selectedSourceSessionId: sourceSessionId,
fetcher: async (url) => {
requestedUrl = url;
return {
ok: true,
status: 200,
json: async () => ({
schema_version: "missioncore.observatory-portable-result-view/v1",
result_id: portableResultId,
source_session_id: sourceSessionId,
result_kind: "recorded-route-analysis",
definition_sha256: definitionSha256,
viewer_capability: portableCapabilityDocument(),
calculation_profile: { name: "M4.9" },
artifact_manifest_id: "d".repeat(64),
artifacts: [{
role: "result-document",
media_type: "application/json",
sha256: "f".repeat(64),
byte_length: 42,
}],
result_document: { verdict: "reviewed" },
}),
};
},
});
assert.equal(
requestedUrl,
`/api/v1/observatory/portable-results/${portableResultId}`,
);
assert.equal(review.kind, "portable-result");
assert.deepEqual(review.resultDocument, { verdict: "reviewed" });
assert.deepEqual(review.artifacts, [{
role: "result-document",
mediaType: "application/json",
sha256: "f".repeat(64),
byteLength: 42,
}]);
});
test("Observatory run admission remains metadata-only until explicit UI activation", async () => {
const source = await readFile(
new URL("../src/core/observatory/recordedRun.ts", import.meta.url),
@@ -99,6 +99,10 @@ test("Observatory mounts the one shared canonical replay only after explicit adm
assert.doesNotMatch(workspace, /observatory-evidence-card[^>]*tone="soft"/);
assert.match(workspace, /Открыть визуальный разбор/);
assert.match(workspace, /replay\.kind === "ready"[\s\S]*<CanonicalVegetationRerunReplay/);
assert.match(workspace, /replay\.review\.kind === "canonical-recorded-rerun"[\s\S]*РЕЗУЛЬТАТ \/ ПРОВЕРЕННЫЙ ДОКУМЕНТ/);
assert.match(workspace, /Связанных артефактов: \{replay\.review\.artifacts\.length\}/);
assert.match(workspace, /<summary>Документ результата<\/summary>/);
assert.doesNotMatch(workspace, /UNIVERSAL VIEWER|content-addressed artifacts/);
assert.match(workspace, /Проверяем точную связь результата/);
assert.match(workspace, /role="alert"/);
assert.match(workspace, /Повторить/);
@@ -247,12 +251,18 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
workspace,
/"preemption-pending": \{[\s\S]*label: "Ждём подтверждения остановки"/,
);
assert.doesNotMatch(setupHook, /Promise\.allSettled/);
assert.match(setupHook, /publishSetupCatalog\(\s*legacyCatalog/);
assert.match(setupHook, /preserveUnknownSelection: true/);
assert.match(setupHook, /Promise\.all\(\[legacyResult, portableResult\]\)/);
assert.match(
setupHook,
/portableProfileNames[\s\S]*legacy\.setups\.filter\([\s\S]*!portableProfileNames\.has\(setup\.displayName\)[\s\S]*\.\.\.portable\.setups/,
/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 готов к проверке/);
@@ -270,7 +280,11 @@ test("Observatory keeps one compact selector axis without the obsolete setup det
assert.match(jobsHook, /return `observatory-ui:\$\{entropy\}`;/);
assert.doesNotMatch(jobsHook, /observatory-ui:[^`]*sourceSessionId|\.slice\(0, 160\)/);
assert.match(workspace, /recordedJobsController\.refresh\(\)/);
assert.doesNotMatch(`${workspace}\n${jobsHook}`, /setInterval|setTimeout/);
assert.match(jobsHook, /POLL_INTERVAL_MS = 1_500/);
assert.match(jobsHook, /globalThis\.setTimeout/);
assert.doesNotMatch(jobsHook, /setInterval/);
assert.match(workspace, /job\.publication\.state === "published"/);
assert.match(workspace, /void controller\.refresh\(\);[\s\S]*setupController\.refresh\(\);/);
assert.match(
styles,
/\.observatory-catalog-bar__controls \{[\s\S]*min-width: 0;[\s\S]*flex: 1 1 auto;[\s\S]*flex-wrap: nowrap;[\s\S]*justify-content: flex-end;/,