You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Persistence closed loop: Workspace -> Project -> WorkflowDraft -> immutable WorkflowVersion. Draft updates require expected_revision (409 on stale write). Publish requires a passed ValidationRun; each validation run is single-use (409 if already consumed, 409 if the draft moved on since the run was taken).
Dify/n8n import: n8n's connections dict (keyed by node name, {main: [[{node, type, index}]]}) is flattened into the same {id, source, target} edge shape everything else uses. Imported nodes get tagged with ui.externalWorkflow (runtime, graph name, raw snapshot) and, for n8n, ui.n8n provenance. Import is a preview -> Validation Run -> publish gate, same as native drafts.
Recursive Package compiler: nodes can nest packages inside packages. Scoped ids look like a::b::c. Hard cap at depth 16 (package_nesting_limit_exceeded past that). Cycle detection runs independently per nesting level. Depth-1 behavior is byte-identical to before this change (regression-tested).
Compatibility tracer + validation execution: reuses the existing WorkflowRun/event infra rather than a new subsystem. run_validation compiles, executes, and either strict-matches an expected-event fixture or does a loose passthrough check.
WorkspaceSettings: timezone, deterministic_simulation, max_items_per_run, with migration + CRUD API.
New router: backend/api/v1/workspaces.py. New models: backend/models/workflow_authoring.py, backend/models/validation_run.py. Migration r5s6t7u8v9w0 (head, on top of m2n3o4p5q6r7).
Test commands actually run
uv run pytest -m "not live" -q -> 1636 passed, 5 failed, 1 skipped, 10 deselected. The 5 failures are a pre-existing Windows/GBK locale codec error unrelated to this branch (backend/api/v1/nodes.py install-script endpoint reading a non-ASCII byte in scripts/install-agent.sh under a GBK locale) — none of the failing tests touch any file this branch modifies.
uv run pytest tests/integration/test_workflow_authoring_api.py -v --no-cov -> 6 passed.
uv run pytest tests/integration/test_workflow_external_import_api.py -v -> 3 passed.
uv run alembic upgrade head against a scratch SQLite DB -> applies cleanly.
Honest limitations / things Codex should know before continuing
The compatibility runtime is simulated, not real Dify/n8n worker dispatch. Import converts an external graph into native OpenCLI capability nodes and runs them through the existing compiler/tracer. There's no live Dify or n8n execution engine wired in anywhere — "compatibility" here means shape/provenance compatibility on import, not runtime interop. If real interop is wanted, that's a separate, much bigger piece of work (actual Dify/n8n API clients, credential handling, async job polling).
n8n-origin classification is reachable but not exercised by the importer.resolve_node_origin() checks catalogId before ui.n8n, and the importer always assigns a recognized catalog id (merge/normalize/fallback). So importer output always classifies as node_library origin, never n8n, even though the node carries ui.n8n provenance. The n8n branch itself works (unit-tested directly), it's just dead code from the importer's current call sites. Worth a product decision: should imported n8n nodes classify as n8n origin for UI/filtering purposes? If so, resolve_node_origin precedence needs to flip, or the importer needs to stop assigning a catalog id for genuinely-unmapped types.
Package nesting below depth 1 doesn't get tracer lifecycle events._package_metadata (used for package-lifecycle events in the tracer) only runs on top-level project.nodes. Nested packages compile and validate correctly at any depth up to 16, but flatten into plain leaf nodes for run/trace purposes — no nested-package-entered/exited events. Deliberate scope cut, not a bug, but should be called out before anyone assumes trace output reflects real nesting depth.
Coverage gate: the repo-wide pytest coverage gate sits around 90% currently; the new code is 86-95% covered per module (see services/workflow_authoring_service.py at 86%, external_importer.py at 91%) — mostly IntegrityError/race-condition branches not exercised, not missing feature coverage.
Nothing here touches the frontend. This is backend/migration/test only, on top of latest main at the time of push (6e09973).
Activity
2233admin commentedon Jul 12, 2026
Pushed: branch
feat/workflow-persistence-closed-loop, commitc326a2ace5882d226f1753bf65df0560b33209af.What's done
Workspace->Project->WorkflowDraft-> immutableWorkflowVersion. Draft updates requireexpected_revision(409 on stale write). Publish requires a passedValidationRun; each validation run is single-use (409 if already consumed, 409 if the draft moved on since the run was taken).connectionsdict (keyed by node name,{main: [[{node, type, index}]]}) is flattened into the same{id, source, target}edge shape everything else uses. Imported nodes get tagged withui.externalWorkflow(runtime, graph name, raw snapshot) and, for n8n,ui.n8nprovenance. Import is a preview -> Validation Run -> publish gate, same as native drafts.a::b::c. Hard cap at depth 16 (package_nesting_limit_exceededpast that). Cycle detection runs independently per nesting level. Depth-1 behavior is byte-identical to before this change (regression-tested).WorkflowRun/event infra rather than a new subsystem.run_validationcompiles, executes, and either strict-matches an expected-event fixture or does a loose passthrough check.timezone,deterministic_simulation,max_items_per_run, with migration + CRUD API.New router:
backend/api/v1/workspaces.py. New models:backend/models/workflow_authoring.py,backend/models/validation_run.py. Migrationr5s6t7u8v9w0(head, on top ofm2n3o4p5q6r7).Test commands actually run
uv run pytest -m "not live" -q-> 1636 passed, 5 failed, 1 skipped, 10 deselected. The 5 failures are a pre-existing Windows/GBK locale codec error unrelated to this branch (backend/api/v1/nodes.pyinstall-script endpoint reading a non-ASCII byte inscripts/install-agent.shunder a GBK locale) — none of the failing tests touch any file this branch modifies.uv run pytest tests/integration/test_workflow_authoring_api.py -v --no-cov-> 6 passed.uv run pytest tests/integration/test_workflow_external_import_api.py -v-> 3 passed.uv run alembic upgrade headagainst a scratch SQLite DB -> applies cleanly.Honest limitations / things Codex should know before continuing
resolve_node_origin()checkscatalogIdbeforeui.n8n, and the importer always assigns a recognized catalog id (merge/normalize/fallback). So importer output always classifies asnode_libraryorigin, nevern8n, even though the node carriesui.n8nprovenance. Then8nbranch itself works (unit-tested directly), it's just dead code from the importer's current call sites. Worth a product decision: should imported n8n nodes classify asn8norigin for UI/filtering purposes? If so,resolve_node_originprecedence needs to flip, or the importer needs to stop assigning a catalog id for genuinely-unmapped types._package_metadata(used for package-lifecycle events in the tracer) only runs on top-levelproject.nodes. Nested packages compile and validate correctly at any depth up to 16, but flatten into plain leaf nodes for run/trace purposes — no nested-package-entered/exited events. Deliberate scope cut, not a bug, but should be called out before anyone assumes trace output reflects real nesting depth.services/workflow_authoring_service.pyat 86%,external_importer.pyat 91%) — mostly IntegrityError/race-condition branches not exercised, not missing feature coverage.mainat the time of push (6e09973).Handing off — Codex, please pick up from here.
docs(verification): record release issue evidence