Add document I/O to workflows and surface run artifacts

Let workflow runs consume and produce documents end to end: bridge uploaded
attachments into run-scoped artifacts so nodes receive the input documents
(with a per-run cap and server-computed size/sha256, and the run row pre-created
so produced artifacts are authorized during the run); emit the run id to the
client and add a builder panel that lists, previews, and downloads a run's
artifacts; and allow attaching documents to a Preview run via the existing
upload flow.

Also fixes issues a compliance workflow surfaced: attachment ownership now keys
on the raw identity instead of a sanitized one (the sanitized form could not be
read back and could collide across users); workflow code nodes read prior state
from a state.json data file instead of templating it into the program, so
untrusted document content can never be interpolated into executed code;
structured node output wrapped in code fences is recovered; and the live
speech-to-text ownership check compares the raw identity.
This commit is contained in:
Alex committed 2026-06-24 22:50:37 +01:00
1 parent 396eb92ab0
commit 5a87fa6258
20 files changed
+1186 -47

No files matched your search

@@ -65,6 +65,21 @@ class TestExecuteLoop:
events = list(engine.execute({}, "query"))
assert any(e.get("type") == "error" and "start node" in e.get("error", "") for e in events)
@pytest.mark.unit
def test_emits_workflow_run_id_first(self):
"""A ``workflow_run`` event carrying the run id precedes any step event."""
nodes = [
_make_node("n1", NodeType.START, "Start"),
_make_node("n2", NodeType.END, "End", config={"config": {}}),
]
edges = [_make_edge("e1", "n1", "n2")]
engine = WorkflowEngine(_make_graph(nodes, edges), _make_agent())
events = list(engine.execute({}, "hello"))
run_events = [e for e in events if e.get("type") == "workflow_run"]
assert len(run_events) == 1
assert run_events[0]["workflow_run_id"] == engine.workflow_run_id
assert events[0]["type"] == "workflow_run"
@pytest.mark.unit
def test_start_to_end(self):
nodes = [