فهرست منبع

docs: record the conventions the Tier 1 nodes settled on

fszontagh 1 ماه پیش
والد
کامیت
cf270788db
1فایلهای تغییر یافته به همراه41 افزوده شده و 0 حذف شده
  1. 41 0
      docs/superpowers/plans/2026-08-04-tier-1-nodes.md

+ 41 - 0
docs/superpowers/plans/2026-08-04-tier-1-nodes.md

@@ -3030,6 +3030,47 @@ not: with the marker, exactly one output carries data and everything downstream
 of the others is skipped. Without it, every named output the node returns is
 live at once, which is how `filter` sends kept and discarded items down two
 paths in the same run.
+
+## Conventions These Nodes Follow
+
+Four rules emerged while building the Tier 1 set. New nodes should follow them.
+
+**Fail loudly rather than drop data.** A node that cannot do what was asked
+throws, with a message naming what it received. `set-fields` throws when
+keep-all mode is handed an array instead of an object; its dotted paths refuse
+to overwrite a value they would otherwise destroy; `merge` throws when an item
+lacks the key it was told to combine on. The alternative - returning an empty
+object, or bucketing everything under the string `undefined` - produces a
+workflow that keeps running and quietly produces wrong data.
+
+**Guard a configurable output name against your own reserved keys.** A node that
+returns fixed keys alongside a user-named field must reject a name that would
+collide, throwing early:
+
+```javascript
+if (outputField === 'count' || outputField === 'groups') {
+    throw new Error('Aggregate: outputField cannot be "' + outputField +
+        '", which is a reserved output name for this node. Pick another name.');
+}
+```
+
+`aggregate`, `sort-limit-dedupe` and `datetime` all need this. `template` and
+`json` do not, because they return exactly one key and there is nothing to
+collide with - do not add the guard where it protects nothing.
+
+**Config values arrive already evaluated.** The engine resolves `{{...}}` in
+node config before `execute()` runs, and a config string that is exactly one
+expression keeps its native type. So a field typed as a string in the schema can
+legitimately hold an array at run time, which is why several nodes begin with
+`if (Array.isArray(inputField))`. That branch is not dead code. Nodes must not
+re-interpolate config values.
+
+**Read paths with the shared helper.** Use
+`smartbotic.utils.getFieldValue(data, path)` rather than writing a private path
+walker. Two older nodes, `if-condition` and `loop`, still carry their own
+divergent copies - `loop` silently skips a leading `data.` segment and
+`if-condition` does not - and that inconsistency is exactly what the shared
+helper exists to stop spreading.
 ````
 
 - [ ] **Step 6: Commit**