feat(wizard): produce fully startable EA2 flows

Reverse-engineered the EA2 runtime startability contract by diffing
pr_to_po_def (startable) against wizard-created drafts (500). Minimum
shape required:

  Parent flow (kind=definition, status=published)
  + Step flow (kind=definition, status=published, dispatch_kind=human)
  + View doc (kind=definition, status=active, layout={fields,layout:form})
  + Version doc (kind=definition, status=active)
  + defines edge: parent -> step (role=child)
  + defines edge: step -> step (role=dispatch self-edge)
  + defines edge: step -> view (role=presentation)
  + governs edge collection 'governs': version -> parent (role=governs)

wizardApi.ops gains: createView, createVersion, childEdge, dispatchEdge,
presentationEdge, governsEdge. createStepEdge renamed to childEdge to
reflect the actual EA2 role. Steps now create with status=published
(was draft) so they're visible to the runtime.

Wizard.handleStructureSave emits all of the above in two apply-batches
(creates then wires). Verified live: posted a probe flow with this
contract, /api/runtime/transactions returned 200 + a running
transaction with step_run_id.
This commit is contained in:
2026-06-14 14:42:55 +04:00
parent c779919453
commit b951724c5f
3 changed files with 90 additions and 15 deletions
+20 -11
View File
@@ -103,27 +103,36 @@ export default function Wizard() {
if (!draft.flowKey || !actor) return;
setWorking(true);
try {
const createOps: BatchOperation[] = draft.nodes.map(n =>
const stepCreateOps: BatchOperation[] = draft.nodes.map(n =>
wizardApi.ops.createStep({
display_name: n.display_name,
dispatch_kind: n.dispatch_kind,
agent_capability: n.agent_capability,
})
);
const createRes = await wizardApi.applyBatch(draft.flowKey, createOps, actor);
const createdKeys: string[] = (createRes?.result?.ops || []).map((o: any) => o.key);
const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: createdKeys[i] || n._key }));
const viewCreateOps: BatchOperation[] = draft.nodes.map(n =>
wizardApi.ops.createView({ display_name: n.display_name })
);
const versionCreateOp: BatchOperation = wizardApi.ops.createVersion(draft.name || "Process");
const allCreateOps = [...stepCreateOps, ...viewCreateOps, versionCreateOp];
const linkOps: BatchOperation[] = [
wizardApi.ops.createStepEdge(draft.flowKey!, nodesWithKeys[0]._key),
...nodesWithKeys.slice(0, -1).map((n, i) =>
wizardApi.ops.createStepEdge(n._key, nodesWithKeys[i + 1]._key)
),
const createRes = await wizardApi.applyBatch(draft.flowKey, allCreateOps, actor);
const createdOps: any[] = createRes?.result?.ops || [];
const stepKeys = createdOps.slice(0, draft.nodes.length).map(o => o.key);
const viewKeys = createdOps.slice(draft.nodes.length, draft.nodes.length * 2).map(o => o.key);
const versionKey = createdOps[createdOps.length - 1]?.key;
const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: stepKeys[i] || n._key, viewKey: viewKeys[i] }));
const wireOps: BatchOperation[] = [
...nodesWithKeys.map((n, _i) => wizardApi.ops.childEdge(draft.flowKey!, n._key)),
...nodesWithKeys.map(n => wizardApi.ops.dispatchEdge(n._key)),
...nodesWithKeys.map(n => wizardApi.ops.presentationEdge(n._key, n.viewKey)),
];
if (linkOps.length) await wizardApi.applyBatch(draft.flowKey, linkOps, actor);
if (versionKey) wireOps.push(wizardApi.ops.governsEdge(versionKey, draft.flowKey!));
if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor);
updateDraft({ step: "Generate", nodes: nodesWithKeys });
pushToast("ok", `Saved ${createdKeys.length} steps to EA2`);
pushToast("ok", `Saved ${stepKeys.length} steps with views and version to EA2`);
} catch (err: any) {
pushToast("err", `Save failed: ${err.message}`);
} finally {