feat(orchestrator): observe Page Data validation
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { buildRunEvent, normalizeRunSpec } from './contracts.mjs';
|
||||
import {
|
||||
buildRunEvent,
|
||||
normalizePageDataValidationObservation,
|
||||
normalizeRunSpec,
|
||||
} from './contracts.mjs';
|
||||
import {
|
||||
CODE_RUN_SHADOW_WORKFLOW,
|
||||
createCodeRunShadowGraph,
|
||||
@@ -22,6 +26,12 @@ function graphConfig(runId) {
|
||||
};
|
||||
}
|
||||
|
||||
function validationObservationFingerprint(observation) {
|
||||
if (!observation || typeof observation !== 'object') return '';
|
||||
const { observedAt: _observedAt, ...stable } = observation;
|
||||
return JSON.stringify(stable);
|
||||
}
|
||||
|
||||
function projectSnapshot(runId, snapshot) {
|
||||
const values = snapshot?.values ?? {};
|
||||
if (!values.spec) return null;
|
||||
@@ -34,6 +44,7 @@ function projectSnapshot(runId, snapshot) {
|
||||
phase: values.phase ?? null,
|
||||
plan: values.plan ?? null,
|
||||
result: values.result ?? null,
|
||||
validation: values.validation ?? null,
|
||||
createdAt: values.events?.[0]?.timestamp ?? null,
|
||||
updatedAt: values.events?.at?.(-1)?.timestamp ?? null,
|
||||
};
|
||||
@@ -136,6 +147,49 @@ export function createLangGraphOrchestratorRuntime({
|
||||
);
|
||||
}
|
||||
|
||||
async function recordValidationObservation(runId, input = {}) {
|
||||
const normalizedRunId = String(runId ?? '').trim();
|
||||
if (!normalizedRunId) return null;
|
||||
const snapshot = await getSnapshot(normalizedRunId);
|
||||
const values = snapshot?.values ?? {};
|
||||
if (!values.spec) return null;
|
||||
const projected = projectSnapshot(normalizedRunId, snapshot);
|
||||
if (!TERMINAL_RUN_STATUSES.has(projected.status)) {
|
||||
const error = new Error('Workflow run must be terminal before validation observation');
|
||||
error.code = 'WORKFLOW_VALIDATION_RUN_NOT_TERMINAL';
|
||||
error.status = 409;
|
||||
throw error;
|
||||
}
|
||||
const observation = normalizePageDataValidationObservation(input);
|
||||
const existing = values.validation ?? null;
|
||||
if (existing?.idempotencyKey === observation.idempotencyKey) {
|
||||
if (
|
||||
validationObservationFingerprint(existing)
|
||||
!== validationObservationFingerprint(observation)
|
||||
) {
|
||||
const error = new Error('Validation observation idempotency key conflicts with existing data');
|
||||
error.code = 'WORKFLOW_VALIDATION_IDEMPOTENCY_CONFLICT';
|
||||
error.status = 409;
|
||||
throw error;
|
||||
}
|
||||
return projected;
|
||||
}
|
||||
await graph.updateState(
|
||||
graphConfig(normalizedRunId),
|
||||
{
|
||||
validation: observation,
|
||||
events: [buildRunEvent({
|
||||
runId: normalizedRunId,
|
||||
sequence: (values.events?.length ?? 0) + 1,
|
||||
type: 'workflow_validation_observed',
|
||||
data: observation,
|
||||
})],
|
||||
},
|
||||
'finalize_run',
|
||||
);
|
||||
return getState(normalizedRunId);
|
||||
}
|
||||
|
||||
async function deleteRun(runId) {
|
||||
const normalizedRunId = String(runId ?? '').trim();
|
||||
if (!normalizedRunId) return null;
|
||||
@@ -204,6 +258,7 @@ export function createLangGraphOrchestratorRuntime({
|
||||
},
|
||||
|
||||
getState,
|
||||
recordValidationObservation,
|
||||
|
||||
async listEvents(runId, { after = 0 } = {}) {
|
||||
const normalizedRunId = String(runId ?? '').trim();
|
||||
@@ -397,4 +452,5 @@ export const orchestratorRuntimeInternals = {
|
||||
graphConfig,
|
||||
projectSnapshot,
|
||||
TERMINAL_RUN_STATUSES,
|
||||
validationObservationFingerprint,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user