|
@@ -0,0 +1,166 @@
|
|
|
|
|
+# Configurator node
|
|
|
|
|
+
|
|
|
|
|
+Date: 2026-08-05
|
|
|
|
|
+Status: agreed, not yet implemented
|
|
|
|
|
+
|
|
|
|
|
+## The problem
|
|
|
|
|
+
|
|
|
|
|
+Some nodes carry a lot of configuration. `sdcpp-generate` alone has 23 settings,
|
|
|
|
|
+and around seventy more reachable through its passthrough. Two things follow:
|
|
|
|
|
+
|
|
|
|
|
+- **Variations are unmanageable.** Trying four prompts against the same model
|
|
|
|
|
+ means four copies of the node, each with every other setting duplicated, and
|
|
|
|
|
+ no way to see what actually differs between them.
|
|
|
|
|
+- **Shared settings are repeated.** `serverUrl` and `credentialId` are the same
|
|
|
|
|
+ for every sdcpp node in a workflow, and every one of them stores its own copy.
|
|
|
|
|
+ Changing a server means editing each node.
|
|
|
|
|
+
|
|
|
|
|
+The goal is to lift settings out of a node and into a node of their own, which
|
|
|
|
|
+can then be duplicated for variations and connected to more than one target.
|
|
|
|
|
+
|
|
|
|
|
+## What it is
|
|
|
|
|
+
|
|
|
|
|
+A new node type, **Configurator**. It holds a list of name/value pairs, like
|
|
|
|
|
+Set, and it is built by copying Set - but the two are separate node types with
|
|
|
|
|
+separate purposes. Set puts values into the data stream. Configurator replaces
|
|
|
|
|
+settings on the nodes it is connected to. Set is not modified by this work.
|
|
|
|
|
+
|
|
|
|
|
+A Configurator's output is always configuration. There is no mode switch and no
|
|
|
|
|
+second port: the node type is the meaning.
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+[Configurator: serverUrl, credentialId] ------+
|
|
|
|
|
+ |
|
|
|
|
|
+[Configurator: prompt, steps] ------------------+--> [sdcpp-generate]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Runtime
|
|
|
|
|
+
|
|
|
|
|
+### How the engine recognises it
|
|
|
|
|
+
|
|
|
|
|
+The Configurator returns a `_config` marker:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+return { _config: { serverUrl: 'http://mulan:8077', credentialId: 'cred_abc' } };
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+When collecting a node's input, an incoming result carrying `_config` is not
|
|
|
|
|
+merged into the data. Its contents accumulate into a config overlay for that
|
|
|
|
|
+node instead.
|
|
|
|
|
+
|
|
|
|
|
+This follows the existing marker convention - `_pause`, `_stop`,
|
|
|
|
|
+`_webhookResponse`, `_isLoop` - rather than teaching the engine about node
|
|
|
|
|
+types. Any future node that wants to supply configuration can do so by
|
|
|
|
|
+returning the same marker, and the engine needs no change to allow it.
|
|
|
|
|
+
|
|
|
|
|
+The engine strips the marker before storing, and records what was supplied as
|
|
|
|
|
+`config` on the node's output, so a run shows which values were applied.
|
|
|
|
|
+
|
|
|
|
|
+### Order of assembly
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+stored config
|
|
|
|
|
+ -> schema defaults (existing applyConfigDefaults)
|
|
|
|
|
+ -> the target's own {{expressions}} evaluated
|
|
|
|
|
+ -> overlay applied last, verbatim
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+The overlay goes last and is **not** expression-evaluated. Its values were
|
|
|
|
|
+already computed by the Configurator when it ran. Evaluating them again would
|
|
|
|
|
+mangle any text containing braces, and a prompt is exactly the field where that
|
|
|
|
|
+would happen.
|
|
|
|
|
+
|
|
|
|
|
+### A skipped Configurator contributes nothing
|
|
|
|
|
+
|
|
|
|
|
+A Configurator on a branch that was not taken is skipped, contributes no values,
|
|
|
|
|
+and **must not cause its target to be skipped**.
|
|
|
|
|
+
|
|
|
|
|
+This one rule is what makes variations work. Three Configurators behind a Switch
|
|
|
|
|
+feeding one `sdcpp-generate`: two are skipped, one is live, the target runs with
|
|
|
|
|
+whichever survived. Under the ordinary skip-propagation rule the target would be
|
|
|
|
|
+skipped along with them, and the feature would not work at all.
|
|
|
|
|
+
|
|
|
|
|
+### A config edge carries no data
|
|
|
|
|
+
|
|
|
|
|
+The target takes its data from its ordinary input as usual. If a config edge is
|
|
|
|
|
+a node's only incoming edge, the node runs with empty input rather than being
|
|
|
|
|
+skipped.
|
|
|
|
|
+
|
|
|
|
|
+### More than one Configurator
|
|
|
|
|
+
|
|
|
|
|
+Values from several live Configurators merge, so shared settings and
|
|
|
|
|
+per-variation settings can be separate nodes:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+[Configurator: serverUrl, credentialId] --+
|
|
|
|
|
+ +--> [sdcpp-generate]
|
|
|
|
|
+[Configurator: prompt, steps] --------------+
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Two live Configurators setting the **same key** is a contradiction with no
|
|
|
|
|
+correct answer. It fails, naming the key and both nodes:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Node "sdcpp-generate" is configured twice for "steps":
|
|
|
|
|
+by "Server settings" and by "Variation A". Remove it from one of them.
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Unknown keys are reported, not fatal
|
|
|
|
|
+
|
|
|
|
|
+A key the target's `configSchema` does not have is logged, listed on the node's
|
|
|
|
|
+output as `ignoredKeys`, and otherwise ignored.
|
|
|
|
|
+
|
|
|
|
|
+This is deliberate. A shared Configurator legitimately carries keys that only
|
|
|
|
|
+some of its targets accept - a server Configurator feeding both `sdcpp-health`
|
|
|
|
|
+and `sdcpp-generate` is the intended use. Failing would make the shared case
|
|
|
|
|
+impossible. Reporting keeps a typo visible instead of silent.
|
|
|
|
|
+
|
|
|
|
|
+## Editor
|
|
|
|
|
+
|
|
|
|
|
+**Picking fields.** With a config edge connected, the Configurator's editor
|
|
|
|
|
+offers the fields of the connected target's schema, by title and type. Choosing
|
|
|
|
|
+one adds an entry with the correct key name, carrying the value the target
|
|
|
|
|
+currently has, and clears it on the target.
|
|
|
|
|
+
|
|
|
|
|
+**On the target.** A field supplied by a Configurator renders disabled, labelled
|
|
|
|
|
+`set by "Server settings"`, and clicking the label opens that Configurator.
|
|
|
|
|
+
|
|
|
|
|
+**Warnings on the Configurator.** Two, matching the two ways this goes wrong:
|
|
|
|
|
+
|
|
|
|
|
+- keys that no connected target accepts ("unused")
|
|
|
|
|
+- connected targets that share no keys at all ("probably the wrong node")
|
|
|
|
|
+
|
|
|
|
|
+Neither blocks anything. Both are visible on the node.
|
|
|
|
|
+
|
|
|
|
|
+## Scope of the change
|
|
|
|
|
+
|
|
|
|
|
+- One new node, `nodes/core/configurator.js`, copied from `set-fields`.
|
|
|
|
|
+- Engine: input collection learns `_config`; one merge step beside the existing
|
|
|
|
|
+ `applyConfigDefaults` call. It must be added to the main walk **and** to
|
|
|
|
|
+ `executeLoopBody`, which has now diverged seven times by being forgotten.
|
|
|
|
|
+- Editor: field picker, disabled fields on the target, warnings.
|
|
|
|
|
+- Set, and every other existing node, is untouched.
|
|
|
|
|
+
|
|
|
|
|
+## Testing
|
|
|
|
|
+
|
|
|
|
|
+Fixtures:
|
|
|
|
|
+
|
|
|
|
|
+- an overlay replaces a stored value
|
|
|
|
|
+- a skipped Configurator contributes nothing and the target still runs
|
|
|
|
|
+- two Configurators merge
|
|
|
|
|
+- the same key from two live Configurators fails, naming both
|
|
|
|
|
+- an unknown key is ignored and reported in `ignoredKeys`
|
|
|
|
|
+- an overlay beats a `{{expression}}` in the target's own config
|
|
|
|
|
+- a Configurator inside a loop body applies per iteration
|
|
|
|
|
+
|
|
|
|
|
+## Risks
|
|
|
|
|
+
|
|
|
|
|
+**Credential choice becomes a runtime decision.** `credentialId` overlays like
|
|
|
|
|
+any other value, so which credential a node uses can depend on which branch ran.
|
|
|
|
|
+The per-workflow credential access check still applies and nothing escapes that
|
|
|
|
|
+boundary, but the choice is no longer visible in the stored workflow alone.
|
|
|
|
|
+
|
|
|
|
|
+**A node's stored config stops being the whole truth.** Reading a workflow
|
|
|
|
|
+document no longer tells you what a node will run with. The editor's disabled
|
|
|
|
|
+fields and the recorded `config` on each execution are what close that gap, and
|
|
|
|
|
+they are not optional polish.
|