Browse Source

docs: design for a Configurator node that supplies settings to other nodes

Lifts settings out of a node into a node of their own, so variations can be
duplicated and shared settings such as serverUrl and credentialId can be
connected to several targets at once.

A separate node type from Set, built by copying it. Set puts values into the
data stream; Configurator replaces settings on the nodes it is connected to.
Set is not modified.

Recognised through a _config marker, following the existing _pause / _stop /
_webhookResponse convention rather than teaching the engine about node types.
The overlay is applied last and verbatim, after the target's own expressions,
so a prompt containing braces is not evaluated twice.

Two rules carry the feature: a skipped Configurator contributes nothing and must
not skip its target - which is what makes Switch-selected variations work at all
- and values from several live Configurators merge, with the same key from two
of them failing loudly and naming both.

Unknown keys are reported rather than fatal, because a shared Configurator
legitimately carries keys only some of its targets accept.
fszontagh 1 month ago
parent
commit
04c0ffc497
1 changed files with 166 additions and 0 deletions
  1. 166 0
      docs/superpowers/specs/2026-08-05-configurator-node-design.md

+ 166 - 0
docs/superpowers/specs/2026-08-05-configurator-node-design.md

@@ -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.