161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
buildRunEvent,
|
|
normalizePageDataValidationObservation,
|
|
normalizeRunSpec,
|
|
stableRolloutBucket,
|
|
} from './contracts.mjs';
|
|
import {
|
|
createRemoteWorkflowEngine,
|
|
createWorkflowEngineRegistry,
|
|
} from './engine-registry.mjs';
|
|
|
|
test('orchestrator contracts normalize run specs without exposing framework types', () => {
|
|
const spec = normalizeRunSpec({
|
|
runId: 'run-1',
|
|
requestId: 'request-1',
|
|
workflow: { name: 'code-run-v1', version: 2 },
|
|
subject: { userId: 'user-1' },
|
|
input: { instruction: 'fix tests' },
|
|
});
|
|
assert.equal(spec.version, 'orchestrator-run-v1');
|
|
assert.equal(spec.workflow.name, 'code-run-v1');
|
|
assert.equal(spec.workflow.version, 2);
|
|
assert.equal(spec.subject.userId, 'user-1');
|
|
|
|
const event = buildRunEvent({
|
|
runId: spec.runId,
|
|
sequence: 1,
|
|
type: 'workflow.started',
|
|
});
|
|
assert.equal(event.version, 'orchestrator-event-v1');
|
|
assert.equal(event.type, 'workflow.started');
|
|
assert.equal(stableRolloutBucket('run-1'), stableRolloutBucket('run-1'));
|
|
});
|
|
|
|
test('Page Data validation observations derive a bounded control-plane verdict', () => {
|
|
const observation = normalizePageDataValidationObservation({
|
|
idempotencyKey: 'run-1:page-data-delivery:v1',
|
|
taskType: 'page_data_dev_complex',
|
|
required: true,
|
|
checks: [
|
|
{ id: 'agent_run_completion', status: 'passed' },
|
|
{ id: 'page_data_binding', status: 'passed' },
|
|
{ id: 'page_data_storage_policy', status: 'passed' },
|
|
{ id: 'independent_review', status: 'skipped' },
|
|
],
|
|
metrics: { pageCount: 2, publicationCount: 1 },
|
|
source: 'portal-agent-run',
|
|
observedAt: 123,
|
|
ignoredSensitivePayload: {
|
|
workspacePath: '/private/workspace',
|
|
instruction: 'must not be projected',
|
|
},
|
|
});
|
|
|
|
assert.equal(observation.version, 'page-data-validation-observation-v1');
|
|
assert.equal(observation.verdict, 'passed');
|
|
assert.equal(observation.dataPolicy, 'control-plane-only-v1');
|
|
assert.deepEqual(observation.metrics, { pageCount: 2, publicationCount: 1 });
|
|
assert.equal('ignoredSensitivePayload' in observation, false);
|
|
assert.equal(JSON.stringify(observation).includes('/private/workspace'), false);
|
|
|
|
const failed = normalizePageDataValidationObservation({
|
|
idempotencyKey: 'run-2:page-data-delivery:v1',
|
|
required: true,
|
|
checks: [
|
|
{
|
|
id: 'page_data_storage_policy',
|
|
status: 'failed',
|
|
codes: ['browser_storage_forbidden'],
|
|
},
|
|
],
|
|
});
|
|
assert.equal(failed.verdict, 'failed');
|
|
assert.deepEqual(failed.checks[0].codes, ['BROWSER_STORAGE_FORBIDDEN']);
|
|
|
|
const inconclusive = normalizePageDataValidationObservation({
|
|
idempotencyKey: 'run-3:page-data-delivery:v1',
|
|
required: true,
|
|
checks: [{ id: 'agent_run_completion', status: 'passed' }],
|
|
});
|
|
assert.equal(inconclusive.verdict, 'inconclusive');
|
|
assert.throws(
|
|
() => normalizePageDataValidationObservation({ idempotencyKey: 'missing-checks' }),
|
|
(error) => error.code === 'WORKFLOW_VALIDATION_INVALID' && error.status === 422,
|
|
);
|
|
});
|
|
|
|
test('workflow engine registry enforces the framework-neutral contract', () => {
|
|
const engine = {
|
|
id: 'native',
|
|
start() {},
|
|
resume() {},
|
|
cancel() {},
|
|
getState() {},
|
|
async *streamEvents() {},
|
|
};
|
|
const registry = createWorkflowEngineRegistry([engine]);
|
|
assert.equal(registry.has('native'), true);
|
|
assert.equal(registry.get('native'), engine);
|
|
assert.throws(
|
|
() => registry.register({ id: 'broken' }),
|
|
/missing methods/,
|
|
);
|
|
});
|
|
|
|
test('remote workflow engine speaks only the versioned orchestrator HTTP contract', async () => {
|
|
const calls = [];
|
|
const engine = createRemoteWorkflowEngine({
|
|
baseUrl: 'http://orchestrator.internal',
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url, init });
|
|
return {
|
|
ok: true,
|
|
async json() {
|
|
return url.includes('/events')
|
|
? { events: [{ type: 'run.succeeded' }] }
|
|
: { ok: true };
|
|
},
|
|
};
|
|
},
|
|
});
|
|
await engine.start({ runId: 'run-1' });
|
|
await engine.resume('run-1', { approvalId: 'approval-1', decision: 'approve' });
|
|
await engine.cancel('run-1');
|
|
await engine.getState('run-1');
|
|
await engine.recordValidationObservation('run-1', {
|
|
idempotencyKey: 'run-1:page-data-delivery:v1',
|
|
checks: [{ id: 'agent_run_completion', status: 'passed' }],
|
|
});
|
|
const events = [];
|
|
for await (const event of engine.streamEvents('run-1')) events.push(event);
|
|
await engine.getExecutorJob('run-1:executor-preview');
|
|
const executorEvents = [];
|
|
for await (
|
|
const event of engine.streamExecutorJobEvents('run-1:executor-preview', 2)
|
|
) {
|
|
executorEvents.push(event);
|
|
}
|
|
|
|
assert.deepEqual(calls.map((call) => [call.init?.method ?? 'GET', call.url]), [
|
|
['POST', 'http://orchestrator.internal/v1/runs'],
|
|
['POST', 'http://orchestrator.internal/v1/runs/run-1/resume'],
|
|
['POST', 'http://orchestrator.internal/v1/runs/run-1/cancel'],
|
|
['GET', 'http://orchestrator.internal/v1/runs/run-1'],
|
|
[
|
|
'POST',
|
|
'http://orchestrator.internal/v1/runs/run-1/validation-observations',
|
|
],
|
|
['GET', 'http://orchestrator.internal/v1/runs/run-1/events'],
|
|
['GET', 'http://orchestrator.internal/v1/executor-jobs/run-1%3Aexecutor-preview'],
|
|
[
|
|
'GET',
|
|
'http://orchestrator.internal/v1/executor-jobs/run-1%3Aexecutor-preview/events?after=2',
|
|
],
|
|
]);
|
|
assert.deepEqual(events, [{ type: 'run.succeeded' }]);
|
|
assert.deepEqual(executorEvents, [{ type: 'run.succeeded' }]);
|
|
});
|