Repository navigation
fix(lint): a flow condition's bare-name type verdict no longer refuses a name a screen field holds, and warns after a node that can bind any name - #30067
Conversation
…e a screen field or an open binder can hold the name Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzicfrt7Yr8FaVdDsXD
… the publish door; changeset and docs row Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzicfrt7Yr8FaVdDsXD
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin efab8afc9317ac00b0e9fa3fafdd856a64241216 && git checkout efab8afc9317ac00b0e9fa3fafdd856a64241216
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d79eecfe5b78598f9117726b542615ca2332d58f 71da71dfbe8d9f3d4e5175b3d45d0b8a4ae09f28 && git checkout -B drift-repro d79eecfe5b78598f9117726b542615ca2332d58f && git merge --no-ff 71da71dfbe8d9f3d4e5175b3d45d0b8a4ae09f28
node scripts/docs-audit/affected-docs.mjs --json d79eecfe5b78598f9117726b542615ca2332d58f
|
|
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5 席位核对Verdict: ACCEPT at head
|
Requested by Yibo Wang · project thread
Fixes #30025
维护者速读
screen节点声明的字段名(恢复时按原名写入变量)不再当作触发对象的字段做类型判断;流程中有能写任意变量名的节点(script、connector_action、wait、subflow、map、无字段契约的 screen)时,裸名的类型不匹配降为type-mismatch-run-may-fail警告。record.x的读取照旧报错。null-guard 判词的裸名集合同样剔除 screen 字段名。title,下游边写title > 5,运行时读到的是收集值、条件能跑,发布门却按文本字段拒绝type-mismatch-run-fails,属误拒。开放绑定节点之后同理:构建期证明不了裸名就是字段。record.x读的是哪条记录,不改变节点是否重绑裸名,所以没有这类节点的记录触发流程,在每道门上的裸名拒绝都不变(expression gate: a type mismatch in a record-triggered flow condition (and in an advisory validation rule) is served a verdict whose text is false for that role #29926、expression gate: flow-condition type-mismatch verdicts still miss or misdescribe three sites after #29926 (non-record-triggered flows, record.x, decision branches) and MCP validate_expression cannot carry severity or trigger kind #29959 的发布门钉照旧通过)。回滚即 revert,无数据迁移。packages/lint/src/validate-expressions.ts(flowBare的构建处与旁注)、packages/formula/src/validate.ts(bareNamesUnproven与同源的 record 根回查)、packages/lint/src/flow-variable-scope.ts(collectScreenFieldNames)。pnpm --filter @objectstack/objectql exec vitest run src/publish-type-mismatch-fail-closed.test.ts,预期 12 条全过:screen 字段持有的裸name > 5发布无判词;script节点之后的裸name > 5发布带警告;两者后面的record.name > 5仍被拒;expression gate: a type mismatch in a record-triggered flow condition (and in an advisory validation rule) is served a verdict whose text is false for that role #29926/expression gate: flow-condition type-mismatch verdicts still miss or misdescribe three sites after #29926 (non-record-triggered flows, record.x, decision branches) and MCP validate_expression cannot carry severity or trigger kind #29959 的原有拒绝不变。Clause-②: yes (widening) — the author-time gate (
os build,os validate, the package publish and the flow publish door) no longer refuses, on a record-triggered flow (or a time-relative sweep of its own object), atype-mismatch-run-failson a BARE condition name that ascreennode's declared field holds (no verdict now), nor on a bare name in a flow with a node that can bind a name no reader sees (script,connector_action,wait,subflow,map, a screen with no input contract), which is now thetype-mismatch-run-may-failwarning. Arecord.<field>/previous.<field>read keeps its refusal, and nothing new is refused. The null-guard verdict's bare names also leave a screen's field names out. Added beside them: the optionalbareNamesUnprovenkey of@objectstack/formula'sExprSchemaHint, andcollectScreenFieldNamesin@objectstack/lint's flow-variable-scope module.Metadata-protocol: none
Skills-docs: updated here —
content/docs/data-modeling/formulas.mdx(the type-soundness row names the screen field and the open binder);skills/**and the MCP skill text teach no bare flow-condition name a screen or an open binder holds, so no skill or eval moves.What changes
Measured on
mainatc23501b2. Two kinds of name a bare flow-condition read can hold instead of the trigger record's field were still typed as the field, so a record-triggered flow was refusedtype-mismatch-run-failson a condition that runs:screennode's declared fieldnameapplyResumeSignal(service-automation/src/engine.ts) lands the resume bag'svariablesunder their plain names, over the flattened fieldtype-mismatch-run-failsExprSchemaHint.flowVariables); no verdictscript,connector_action,wait,subflow,map, a screen with no input contract;flowCelRootScope'sCLOSED_BINDER_NODE_TYPES)type-mismatch-run-failstype-mismatch-run-may-failrecord.<field>/previous.<field>in either flowScreen fields (
@objectstack/lint). NewcollectScreenFieldNames(graphs)inflow-variable-scope.tsreads everyscreennode'sconfig.fields[].nameacross every graph. The flow walk unions it into the names it hands the type check (flowVariables), and subtracts it from the null-guard twin'snullGuardBare.collectFlowVariableNamesis unchanged, so the #14089 shadowing warning and its other consumers are not moved.Open binders (
@objectstack/formula+ lint). New optionalExprSchemaHint.bareNamesUnproven(flattened scope only). IntypeSoundnessIssue, atype-mismatchon a bare name (norecord/previousroot) under a declared outcome takes no outcome when the key is set, so it is the existing run-may-fail warning. If arecord.<field>mismatch sits in the same source behind the bare one (name > 5 && record.title > 5),firstTypeMismatchis asked again with every bare name leftdyn, and that record-root mismatch keeps the error. Date verdicts are untouched.Which predicate (a measured departure from the order's literal wording, agreed with the seat). The order said to use
rootScope.provable, the null-guard twin's predicate. Measured: at the runtime publish gaterootScopeis built withperWriteSnapshotEntrance(), which opens EVERY flow write, so the literal reading would have dropped every bare-name refusal on every record-triggered flow at the Studio/package publish door to a warning;publish-type-mismatch-fail-closed.test.tsgoes red on the #29926 start pin and the #29959 decision pin (ablation A5 below). The flow walk now asks the same judge,flowCelRootScope(flow, graphs, objectName, fieldIndex), with no entrance: only a NODE that can bind a name stands the bare verdict down. An entrance (and the per-write snapshot, which sees none) changes which record both the bare name andrecord.<field>read, not whether a node rebinds the bare name, so it moves neither. The null-guard twin keeps standing its bare names down on the wholerootScope, as before. A code comment beside the call says so.Ledger.
faultOutcomeOf's ledger docblock gains the screen field in the variable row and a row for the open-binder stand-down;validate-null-guards.tspoint 2 names the screen fields and the shared judge.Not done (optional item). The engine's registration advisory pass (
flow-registration-checks.ts) still passes noflowVariables:@objectstack/service-automationdoes not depend on@objectstack/lint(wherecollectFlowVariableNames/collectScreenFieldNameslive), and adding that dependency is outside this card. Its line is a log, never a refusal.Tests
packages/formula/src/validate.test.ts: withbareNamesUnprovenandrun-fails, barename > 5/is_active > 0/name * 2 > 1→ onetype-mismatch-run-may-failwarning;record.name > 5,previous.is_active > 0andname > 5 && record.title > 5→type-mismatch-run-failsnaming the record read; controls: key absent → the error;flowVariablesstill untypes; arecord-scopedfail-closedsite ignores the key.packages/lint/src/validate-expressions.test.ts, newdescribe"a bare name a screen or an open binder can hold (lint: a flow condition's bare-name type verdict ignores names a screen node or an open binder (script, connector_action, wait, subflow, map) binds, so a record-triggered flow can be refusedtype-mismatch-run-failson a name that holds the bound value #30025)": record-triggered flow, screen fieldtitle: numberbefore atitle > 5edge and atitle > 5decision branch → no type verdict, only therecord.title > 5branch refused; the same after ascriptnode and after anobject-formscreen → two warnings, one error; the null-guard twin: nullablebudgetwith a screen collectingbudget→ no nullable refusal, control screen collectingnote→ refused. The validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017 receiver meta-test tables theflowBare/screenFieldsplumbing receivers.packages/objectql/src/publish-type-mismatch-fail-closed.test.ts, new top-leveldescribein the existing harness, through the realsaveMetaItem(draft) +publishPackageDraftsdoor: screen-held barename > 5publishes with no type advisory; barename > 5after ascriptnode publishes with the run-may-fail warning;record.name > 5after either node is refused. The existing expression gate: a type mismatch in a record-triggered flow condition (and in an advisory validation rule) is served a verdict whose text is false for that role #29926 / expression gate: flow-condition type-mismatch verdicts still miss or misdescribe three sites after #29926 (non-record-triggered flows, record.x, decision branches) and MCP validate_expression cannot carry severity or trigger kind #29959 refusals in the same file are the publish-door pins that the bare refusal still holds without such a node.Ablations (each restored, tree clean after):
flowBare.variablesunproven: falseoutcome = undefinedalways)name > 5 && record.title > 5)nullGuardBareunproven: !rootScope.provablename > 5decision branch…"unproven: falseat the publish doorname > 5after a script node with the run-may-fail warning"Verification
All at head
71da71df, underos-verify-lock(shared box). Basec23501b2;origin/mainis 3 commits ahead, none on the touched files, andgit merge-treereports no conflict.node scripts/pm/dispatch-gates.mjs --commands: 89 derived families, each run,CMD :: exit Nrecorded, reconciled with--ran: 89 derived, 88 run, 1 NOT-MEASURED, 0 UNRUN. 88 exit 0 (three of them,check-plugin-teardown-shape --self-test,check:skill-examplesandcheck:lean-entry-closure, first exited 3 on a missing prerequisite and pass aftergit fetch --unshallowand building objectql / client-react). NOT MEASURED:check:dual-build-cjs-loads, reason: it reads every package's dist (70 missing) and needs a fullpnpm build; CI's run is the measurement.Acceptance notes
collectFlowVariableNames: a screen field name is excluded at every condition of the flow, the start condition included, although a start condition runs before any screen. That is a missed verdict (the safe direction), the same trade expression gate: flow-condition type-mismatch verdicts still miss or misdescribe three sites after #29926 (non-record-triggered flows, record.x, decision branches) and MCP validate_expression cannot carry severity or trigger kind #29959 made for node ids, for the reasoncollectFlowVariableNames' docblock gives (one variable map per run; no traversal-order verdicts).validate-expressionshas no flow leg for bare identifiers — a bare field reference in a flow condition passesobjectstack validateclean #14089 shadowing warning (warnShadowedFieldReads) still names declared variables and node ids only, not screen fields. Not judged here.date - date + 1(date-arithmetic) after an open binder is still an error: the card covers the type-mismatch verdict, and that one is not an outcome verdict.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (not touched)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzicfrt7Yr8FaVdDsXD