refactor(service-automation): deprecate non-canonical CRUD node config-key aliases#2425
Merged
Conversation
…g-key aliases Flow node `config` is `z.record(z.string(), z.unknown())` in spec, so the executor is the only runtime gate on key names. The CRUD nodes quietly accepted `object` for `objectName` and `filters` for `filter` via `cfg.canonical ?? cfg.alias` — a Postel's-law tolerance that fossilizes the wrong shape into a de-facto second contract and hides metadata-generation bugs. Converge on the canonical key (matching the `fields`/`fieldValues` decision: reject the wrong shape at author/publish time, don't absorb it at runtime). This is the deprecation-window step, not a removal: - add `readAliasedConfig()` — returns the canonical value, else the alias with a one-time `logger.warn` naming the canonical key (graph-lint can't reach flows already stored in prod that are never re-published; the warn covers that gap). - wire it into get/create/update/delete_record for `objectName`/`filter`. - fix the one authoring source that taught the alias: skills/objectstack-automation SKILL.md update_record example `object:` -> `objectName:`. No alias is straight-removed — all are plausibly present in stored prod flows. Removal is a tracked follow-up gated on the warn window + a cloud graph-lint rule. Siblings (notify to/subject/body, script inputs/function, wait duration/signal, assignment name/key, http-dispatcher trigger `object`) get the same treatment in follow-ups. Tests: service-automation 225 pass (incl. new crud-config-aliases.test.ts), spec 6600 pass, tsup DTS build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
June 28, 2026 13:23
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.
Audit: "lenient input" config-key aliases in automation node executors
FlowNodeSchema.configisz.record(z.string(), z.unknown())(flow.zod.ts:102), so the spec blesses no particular config key — the executor is the only runtime gate on key names. That makes everycfg.canonical ?? cfg.aliasin a node executor a Postel's-law tolerance that fossilizes a wrong shape into a de-facto second contract and hides metadata-generation bugs. This PR is Step 1 of converging them: it follows thefields/fieldValuesprecedent (reject the wrong shape at author/publish time; don't absorb it at runtime).What this PR changes (clear-cut, zero-risk)
readAliasedConfig()helper — returns the canonical value; on the alias path, emits a one-timelogger.warnnaming the canonical key, then keeps working. (graph-lint runs only at publish, so it can't reach flows already stored in prod that are never re-published — the runtime warn covers that population.)objectNameandfilter.skills/objectstack-automation/SKILL.mdupdate_recordexampleobject:→objectName:(every example.flow.tsalready usesobjectName/filter).No alias is removed — all are plausibly present in stored prod flows. Removal is a tracked follow-up gated on (a) the warn window and (b) a cloud
graph-lintrule.Findings table (full audit)
Canonical is established by docs/skill/examples + cloud graph-lint (the spec is
z.record, vacuously permissive). "Debt" = one canonical key + tolerated alias; "Union" = spec genuinely blesses both.crud-nodes.tsobjectName ?? object(get/create/update/delete)z.record(any key)objectNamecanonical (all 8 example flows);objectonly in SKILL.mdcrud-nodes.tsfilter ?? filters(get/update/delete)z.recordfiltercanonical;filterszero usagenotify-node.ts:75-77recipients ?? to,title ?? subject,message ?? bodyz.recordnotifyunused in examplesscreen-nodes.ts:198inputs ?? input(script)z.recordscreen-nodes.ts:135function ?? functionName(script)z.recordwait-node.ts:61,108… ?? loose.duration,… ?? loose.signalwaitEventConfig.timerDuration/signalName(typed)wait-node.tswaitEventConfig.* ?? config.*waitEventConfig; examples author underconfiglogic-nodes.ts:71variable ?? name ?? key(assignment)z.record(+ documented 3-surface normalizer)name/keyarray-item aliasesaction.zod.ts:529p.name ?? p.field(+ refine :70).refine(name || field)blesses bothhttp-dispatcher.ts:2836ctxBody.objectName ?? ctxBody.object(APIPOST /:name/trigger)http-dispatcher.ts:2892inputs ?? variables(resume body)Out of scope (separate convergence tracks — flagged, not folded in)
metadata-protocol/protocol.ts:2060,2077(orderBy ?? sort,filter ?? filters ?? $filter ?? where) andhttp-dispatcher.ts:1792(where ?? filter ?? filters): mostly a legit OData/ObjectQL union;filtersplural is the debt slice. Owner: ObjectQL query options.rest-server.ts(~40 sites, e.g.body.object ?? body.object_name): a deliberate, documented REST body convention. Leave.plugin-approvals/approval-service.ts(raw.flow_node_id ?? raw.current_step), auth/security*_id ?? *Id: reading persisted rows, not author config. Separate (legacy column) concern.service-datasource/default-datasource-driver-factory.ts:92user ?? username:datasource.zodprose saysuser, the postgres/mongo driver schemas sayusername. Fix the spec first (pick one), then converge — not clean runtime debt.Why deprecation window, not straight removal
The spec is
z.record, so the runtime has been the only gate; any tolerated alias may sit in a stored prod flow and there's no telemetry to prove otherwise. Several are documented/intuitive (objectis literally in the skill;to/subjectread as email;filters/input/durationare natural slips). Removal's failure mode is loud-and-safe (create_record: objectName required) rather than corruption — but it's still an availability regression for running flows, which is exactly what the window avoids. So: warn now, remove after the window + a graph-lint rule, and stop teaching/emitting the alias at the source.Tests
@objectstack/service-automation: 225 pass (incl. newcrud-config-aliases.test.ts— alias still resolves, warns once per alias, canonical path silent).@objectstack/spec: 6600 pass.tsupESM/CJS/DTS build clean.Suggested follow-ups
readAliasedConfigwarn to notify/screen/wait/logic aliases (Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3,4,6,8) and thehttp-dispatchertriggerobject(chore: version packages #10).user/usernamespec ambiguity before touching that runtime tolerance.🤖 Generated with Claude Code