feat(automation): resolve & validate script-node callables; function registration (#1870)#1903
Merged
Merged
Conversation
…registration path (#1870) A flow `script` node pointing at an unregistered callable — or declaring no `actionType`/`function` at all — built fine and silently did nothing at runtime. Close the gap on three surfaces: - Runtime: the built-in `script` executor resolves its target (built-in side-effect → registered function → else fail loud). The old `(no-op handler)` success path is removed, so an unwired callable can no longer quietly skip the step. - Registration path: add `AutomationEngine.setFunctionResolver()`/ `resolveFunction()` and bridge it in the plugin's `start()` to ObjectQL's `resolveFunction` (fed by `bundle.functions` / `defineStack({ functions })`). A `script` node can now invoke an authored function by name: `{ type: 'script', config: { function: 'my_fn', inputs: { … } } }`. - Build: `objectstack build` flags a `script` node that names no callable (the `actionType: undefined` repro). Existence is checked at runtime — functions are code, not serialized into the artifact. Tests: new script-executor suite (function resolution, loud failure, bare actionType shorthand, thrown-function surfacing) + CLI build-validation cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… as an empty no-op (#1870) CI Build Core caught that the build-time check (and the runtime executor) treated a `script` node carrying inline JS in `config.script` as target-less, breaking example-crm/showcase builds. Inline `config.script` is a distinct, recognized form — the built-in runtime has no server-side JS sandbox so it does NOT execute it (a separate gap, out of #1870's callable-resolution scope). Now: - build validation accepts `function` | `actionType` | non-empty `script`; - the runtime executor warns loudly that inline scripts aren't executed (and points to `defineStack({ functions })`) instead of failing the step or silently succeeding. Verified example-crm, app-showcase, app-todo all build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1870.
Problem
A flow
script/action node pointing at an unregistered callable (invoke_function→ a function defined nowhere), or declaring noactionType/functionat all (actionType: undefined), built + typechecked fine and then silently did nothing at runtime. There was also no first-class way for an authored package to register a function ascriptnode could call. Found while runtime-testingobjectstack-ai/templateson 9.5.1 (helpdeskai_triage_on_create→helpdesk.aiTriageStub; projectdaily_ai_risk_assessment→pm.aiRiskAssessmentStub; contentpublication_rollupwithactionType: undefined).Fix
Three surfaces, mirroring the loud-validation approach from #1877:
1. Loud runtime resolution — screen-nodes.ts
The built-in
scriptexecutor resolves its target in order:email/slack, logger-backed, preserved) →config.function, or a bareconfig.actionTypethat matches no built-in, so a node pointed straight athelpdesk.aiTriageStubresolves) →The old
[Script:…] (no-op handler) → { success: true }path is gone.2. First-class registration path — engine.ts + plugin.ts
New
AutomationEngine.setFunctionResolver()/resolveFunction(). The plugin'sstart()bridges it to ObjectQL'sresolveFunction, populated frombundle.functions/defineStack({ functions })— the same registry hooks/actions already use. So an authored package registers a function and calls it from a flow:The function receives
{ input, variables, automation, logger }and its return value becomes the node output. Reuses the existingscriptnode (no enum/Studio churn) per the chosen authoring contract.3. Build-time structural check — validate-expressions.ts
objectstack buildnow flags ascriptnode that declares neitheractionTypenorfunction(theactionType: undefinedrepro). Function existence is not checkable at build — functions are code, not serialized into the artifact — so existence is enforced at runtime.Design note
Registration-time (
registerFlow) does not hard-fail on a target-lessscriptnode: ascriptnode can be claimed by a plugin-registered custom executor (existing engine tests rely on this), and the engine can't distinguish built-in vs custom at registration. The guard lives where it's unambiguous — the built-in executor (runtime) andobjectstack build(authored metadata).Tests
screen-nodes.test.ts: built-in side-effect, function resolution + output capture, bare-actionTypeshorthand, loud failure for unregistered function, loud failure for no target, thrown-function surfacing.validate-expressions.test.ts: build flags target-lessscriptnode; acceptsactionType/function.@objectstack/service-automation193 passed ·@objectstack/cli260 passed · production build + DTS clean (the package's pre-existingtsc --noEmittest-fixture errors are unrelated and present onmain).Scope
Slice A (loud validation) + Slice B (registration path) per the issue.
connector_actionalready failed loud and is unchanged.🤖 Generated with Claude Code