56 Commits

Author SHA1 Message Date
Claude
6615c5f7d0 fix(python): fan out only on JSON-array output, not any IEnumerable
The old code iterated every IEnumerable, so `output = "hello"` emitted one
item per character (a string is IEnumerable-of-char) and `output = {"a":1}`
emitted the keys as items. Now the script's output is serialized first and
the ValueKind decides: JSON array → per-element items; anything else →
single item. `output = None` remains an empty emission.

New tests cover string, dict, number, and None shapes.
2026-08-31 11:21:55 +00:00
Claude
3fa541a032 fix(python): surface Python tracebacks in node error + dialog Output
Backend: PythonTransformNode wraps script execution in a try/catch that
runs the exception through IronPython's ExceptionOperations.FormatException.
Users now see a proper Python-style traceback (file, line, type, message)
instead of the bare CLR message (which was often empty or just "unexpected
token"). Source is named "script" for readable File lines in the trace.

Frontend: findLatestIo also returns status + error; NodeEditorDialog
renders a red-bordered traceback block in the Output column when the
node failed, replacing the misleading "No data yet" state.
2026-08-31 10:47:34 +00:00
Claude
16a5225ee3 feat(executor): node pinned_input overrides upstream byPort on manual runs 2026-08-30 14:22:01 +00:00
Claude
e24bdb012b feat(executor): trigger pinned_output overrides triggerItems on manual runs 2026-08-30 14:17:29 +00:00
Claude
8a68b13a70 feat(executor): parse pinned_input/pinned_output on WorkflowNode + WrapPin helper 2026-08-30 14:11:03 +00:00
Claude
85e2339c1d refactor(executor): use action.ConfigSchema directly + dispose transient JsonDocuments 2026-08-30 10:59:01 +00:00
Claude
70f556aff5 feat(executor): coerce templated string leaves to schema-declared primitive types 2026-08-30 10:54:26 +00:00
Claude
46372e8e38 feat(executor): {input.foo} template interpolation on node config strings
Every action/terminal node's config string fields are now run through
TemplateEngine.Render before the node sees them, so operators can pull
values from the incoming item into any config field.

- {input}, {input.a.b}, {input.tags[0]}, {input["weird.key"]}
- {{ / }} escape to literal braces
- Missing paths / null input → empty string (no 500s on absent query params)
- Unrecognised / malformed placeholders pass through so typos are visible
- Strings render raw; numbers/bools stringified; objects/arrays compact JSON

Interpolation runs executor-side (Executor.RunNodeAsync walks ConfigJson
and rewrites string leaves), so every node opts in for free. 20 new tests
(17 TemplateEngine + 2 Executor + 1 end-to-end webhook→WebResponse).
2026-08-29 18:44:03 +00:00
Claude
bc682d8c9f feat(webhook): GET support — query string → JSON body, HEAD safe no-op
POST behaviour unchanged (JSON body). GET on the same URL now triggers the
workflow with the query string collapsed into a JSON object: /wh/wf/hi?a=1&name=x
becomes {"a":"1","name":"x"}, and repeated keys collapse into a string
array (?tag=a&tag=b → {"tag":["a","b"]}). No query string yields {} —
matches the empty-POST-body semantics.

HEAD returns 200 empty so Slack/Discord link previews don't fire the workflow
AND don't flag the URL as broken. Other verbs (PUT/PATCH/DELETE/OPTIONS) still
return 405 JSON with Allow: POST, GET.

Frontend WebhookUrlHint now advertises both verbs and drops the POST prefix
from the displayed URL so the raw URL is copy-and-clickable.

6 new xUnit tests cover: GET-unknown-404, GET-with-query, GET-repeated-keys,
GET-empty-query, HEAD-200, PUT-still-405 (updated Allow header).
2026-08-29 07:51:27 +00:00
Claude
a06e9e0e84 feat(connections): Test button — opens the provider round-trip and reports back
Backend: POST /api/connections/{id}/test decrypts the stored config, calls
provider.OpenAsync with a 10s CancellationTokenSource, closes the returned
handle in a finally, and returns {ok: bool, error?: string}. Always 200
unless the id is unknown (404) — the outcome of the round-trip belongs in
the body, not the status. 2 new xUnit tests cover the 404 path and the
"connection refused" failure path (points at 127.0.0.1:1).

Frontend: Plug icon on each ConnectionsPage row. Click → animated icon
during the round-trip, then an inline pill: 'Connected' (green check) on
success or the truncated error (red X, full text on hover) on failure.
Auto-clears after 6s. Row state is tracked per-id in a plain useState;
the shared react-query cache stays untouched. 1 new Vitest covers both
outcomes via MSW handlers.
2026-08-29 07:41:19 +00:00
Claude
1a1f519144 fix(webhook): GET on /wh/* returns 405 helper page instead of SPA fallback
Previously a browser click on the webhook URL fell through the POST-only
handler to MapFallbackToFile('index.html'), landing the user on the
workflows page and looking like a bug. Now non-POST verbs on the same
route return 405 with Allow: POST — GETs render a small HTML page showing
the correct curl invocation, other methods get JSON. Two new xUnit tests
cover the HTML and JSON branches and assert we don't hand back the SPA.
2026-08-28 20:16:07 +00:00
Claude
4b30d4a44e feat(connections): edit connections via PUT — backend + UI + blank-secret merge
Backend: PUT /api/connections/{id} with UpdateConnectionRequest {Name, Config}.
Provider is immutable; a secret field left blank in the incoming config is
merged from the stored row so users can rename or rotate non-secret fields
without re-typing every credential. Invalidates the ConnectionInstanceRegistry
cache on update so the next GetConnection() re-opens with the fresh config.
409 on unique-name-per-provider collision, 404 on unknown id, 400 on non-
object config. 6 new xUnit tests cover the merge, overwrite, 404, 409, 400
paths plus the happy-path round-trip.

Frontend: ConnectionsPage gains an Edit pencil per row. The form is shared
between create and edit modes; provider select is disabled in edit; secret
fields render blank with an inline hint that leaving them blank keeps the
stored value. One new Vitest covers the edit flow.
2026-08-28 19:50:08 +00:00
Claude
6ffc59870c fix(auth): heal legacy plaintext admin.password_hash_source on boot 2026-08-28 18:54:35 +00:00
Claude
3d9df50f20 feat(api): honour X-Forwarded-Proto so Secure cookies flip on behind Traefik 2026-08-28 18:48:30 +00:00
Claude
03de45cf8e feat(api): serve SPA from wwwroot + SPA fallback for client-side routes 2026-08-28 16:32:16 +00:00
Claude
c4c650e3d0 chore(frontend): scaffold Vite + React 18 + TypeScript under frontend/ 2026-08-28 16:00:34 +00:00
Claude
6065f0c91e feat(api): DI-register 5 nodes + NATS provider; enable pending integration tests 2026-08-28 15:35:43 +00:00
Claude
df64e9e512 feat(nodes): PythonTransformNode (IronPython 3) — items in, output out 2026-08-28 15:22:49 +00:00
Claude
ebd06a6abc feat(nodes): WebResponseNode — resolves webhook TCS with configured payload; move WebhookRouter to Core 2026-08-28 15:20:10 +00:00
Claude
3d7910c5e7 feat(nodes): WebhookNode — registers (workflow,path) route and emits body items 2026-08-28 15:16:51 +00:00
Claude
001c60bf86 feat(nodes): NatsPublishNode — publishes each item as its JSON bytes 2026-08-28 15:13:36 +00:00
Claude
b9a3926be2 feat(nodes): NatsHookNode — trigger emits one execution per message 2026-08-28 15:11:49 +00:00
Claude
b9097b1812 feat(providers): NatsConnectionProvider (NATS.Net) 2026-08-28 15:09:13 +00:00
Claude
5137591b8a feat(engine): RetentionService — hourly executions sweep 2026-08-28 15:07:58 +00:00
Claude
0895dc5f8f feat(api): manual run + executions REST (list + detail with per-node ports) 2026-08-28 15:05:54 +00:00
Claude
850eb4a12c feat(engine): TriggerHost with activation/deactivation + startup abort sweep 2026-08-28 15:03:35 +00:00
Claude
ff54b1f22d feat(api): POST /wh/{workflow-id}/{*path} endpoint 2026-08-28 15:00:24 +00:00
Claude
c88d27294b feat(engine): WebhookRegistry — (workflowId, path) → invocation lookup 2026-08-28 14:58:06 +00:00
Claude
eabd6f175d feat(engine): WebhookRouter (TCS + first-write-wins + timeout) 2026-08-28 14:56:52 +00:00
Claude
159102e27e feat(engine): Executor fan-out + failure propagation + status rollup 2026-08-28 14:52:53 +00:00
Claude
8c58b6574d feat(engine): Executor — linear happy-path (trigger → action → terminal) 2026-08-28 14:49:47 +00:00
Claude
8fd152d977 feat(engine): ConnectionInstanceRegistry with per-id caching + invalidate on delete 2026-08-28 14:46:15 +00:00
Claude
8f4196de51 feat(engine): concrete ExecutionContext + TriggerActivationSink 2026-08-28 14:43:56 +00:00
Claude
37fb16bc43 feat(persistence): executions + node_executions migrations and repositories 2026-08-28 14:41:21 +00:00
Claude
b645d1b2ab feat(api): schema-driven IConnectionSecretPolicy from provider SecretFields 2026-08-28 14:37:50 +00:00
Claude
af905361eb feat(persistence): encrypt admin_config secure keys at rest + direct repo tests 2026-08-28 14:35:05 +00:00
Claude
11aae45000 fix(api): 400 for non-object connection config + null workflow graph; 409 for duplicate connection name 2026-08-28 14:32:30 +00:00
Claude
3ec74b33c9 feat(core): widen INodeContext/ITriggerSink/IConnectionProvider for Plan 2 2026-08-28 14:30:26 +00:00
Claude
f6de4c658b feat(api): fail-fast env parsing for ints + ENCRYPTION_KEY base64 2026-08-28 14:28:47 +00:00
Claude
fe531ac7ec chore: scaffold VolcanicCore.Nodes + VolcanicCore.Providers projects 2026-08-28 14:26:13 +00:00
Claude
57db5a53db feat(api): workflows CRUD + activate/deactivate flag 2026-08-28 12:18:58 +00:00
Claude
89b721fdd0 feat(api): connections CRUD with encrypted storage + secret masking 2026-08-28 12:13:37 +00:00
Claude
caa1e6e834 fix(persistence): use ThrowsAny in tamper test; keep AesGcm exception subclass visible 2026-08-28 12:08:37 +00:00
Claude
62efc3260f feat(persistence): AES-GCM encryption service + key loader 2026-08-28 12:06:49 +00:00
Claude
1ffe98cce0 feat(api): discovery endpoints for node types + connection providers 2026-08-28 12:02:08 +00:00
Claude
857ddd67ea feat(api): NodeRegistry + ConnectionProviderRegistry (DI-collected) 2026-08-28 11:58:38 +00:00
Claude
77a4ef0c35 feat(core): IConnectionProvider contract 2026-08-28 11:54:58 +00:00
Claude
424a24965b feat(core): INodeType + IActionNode + ITriggerNode contracts 2026-08-28 11:54:41 +00:00
Claude
b716aee87c feat(core): Item, JsonSchema, NodeCategory, INodeContext, ITriggerSink 2026-08-28 11:54:21 +00:00
Claude
eb59c87d03 feat(auth): cookie session endpoints + WebApplicationFactory harness 2026-08-28 11:48:45 +00:00