Repository navigation
fix(lint)!: type a time-relative flow's conditions against its swept object when the start names no objectName, and let validate_expression take the swept object - #30276
Conversation
…bject when the start names no objectName Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CaUg3ibF939L2RFB5D
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CaUg3ibF939L2RFB5D
|
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL Contract reviewServed-tier: ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged against the live tree on
② Semver level
③ Boundary flagsFrom os-dev-report 6649735495:
Flags of this review's own:
④ Design judgmentThe narrowing against the published spec on
Findings:
Implemented-by: VERDICT: PASS |
…-orkuas # Conflicts: # packages/lint/src/validate-expressions.test.ts
…y the warning the time-relative note explains Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CaUg3ibF939L2RFB5D
📓 Docs Drift CheckThis PR changes 2 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
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 f40e86b29d9cd49c8379d351171abe29774c7531 && git checkout f40e86b29d9cd49c8379d351171abe29774c7531
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 959c26be1f4179202b0ed42f58076190d4d590d5 52faf73cc8fdc7dbdd5f93e108a1070fde49cc2b && git checkout -B drift-repro 959c26be1f4179202b0ed42f58076190d4d590d5 && git merge --no-ff 52faf73cc8fdc7dbdd5f93e108a1070fde49cc2b
node scripts/docs-audit/affected-docs.mjs --json 959c26be1f4179202b0ed42f58076190d4d590d5
|
Requested by Bill · project thread
domain:server#2 · os-bill · session_01Utp6CaUg3ibF939L2RFB5D
Fixes #30026
Clause-②: yes (narrowing) — the author-time gate (
os build,os validate, the package publish and the flow publish door) refuses atype-mismatchin the start, edge anddecisionconditions of a time-relative flow whose start node names noobjectName, which it passed with no type verdict at all before. Added beside it (the widening half): the optionalsweepObjectinput of thevalidate_expressionMCP tool.Metadata-protocol: narrowed
Skills-docs: updated here — the MCP skill text in
packages/mcp/src/skill-md.ts(the newsweepObjectinput);skills/**teaches no time-relative flow condition with a text-vs-number comparison, so no skill or eval moves.维护者速读
objectName、只在timeRelative.object写了被扫描的对象时,条件现在按被扫描对象的字段做类型检查,文本与数字比较会被发布拦下(以前完全不检查);开始节点写的对象与扫描对象不一致时,仍只给警告,但会多一条说明告诉作者为什么、怎么改;MCP 的validate_expression新增可选输入sweepObject,能给出与发布门一致的判决。record就是被扫描对象的一行,条件里文本与数字比较每次都会让运行失败,但发布时一声不吭;另外 fix(lint)!: a flow condition's text-vs-number comparison gets a true verdict on every flow, record.x read and decision branch #30015 的注释和测试描述了一个根本绑不上的形状(不写timeRelative.object)。packages/lint/src/validate-expressions.ts流程遍历开头的objectName = startObject ?? sweepObject与sweepsTypedObject,同文件流程末尾的说明警告;packages/mcp/src/mcp-http-tools.ts的sweepObject输入与flow_condition判决。pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-expressions.test.ts -t "time-relative",预期 3 个用例通过,其中tr_sweep_only三处条件均为 error。What changes
validateStackExpressions, the flow walk). The object a flow's conditions are typed against is the start node'sobjectName, else, on a time-relative flow,timeRelative.object.TimeRelativeTriggerSchemarequires that key, andtime-relative-trigger.tslaunches every run with a row of that object asrecord(engine.find(desc.object, …), then{ record: row }), whatever the start node names. So a sweep whose start names noobjectNameis now typed against the swept object at every flow site that reads the typed object: start, edge anddecisionconditions (field existence, the type check with therun-failsoutcome,record.FIELDreads), the shadowed-variable warning, and the root check's did-you-mean order. The null-guard twin is unchanged (record-triggered flows only).sweepsTypedObjectis nowtimeRelative.object === typed objectand nothing else. The old "else the start node's" arm graded a descriptor with noobjectas a sweep of the start object; that descriptor never binds (flow-time-relative-descriptor-invalid, error), so no run of it binds a row and it now gets no declared outcome (the warning). The ledger comment says so.objectNameis nottimeRelative.object, thetype-mismatch-run-may-failwarning stays, and the flow gets one more warning atflows[i].nodes[j].config.objectNamesaying why: the conditions are checked against the start object's fields while each run's record is a row of the swept one; setobjectNameto the swept object or remove it. It is pushed once per flow, and only when the flow was served a-run-may-failwarning (counted incheck()by the warning'scode, so the date verdicts'-run-may-failarm from expression gate: a date-arithmetic verdict in a flow condition still says the value "silently evaluates to null", though the run fails #30016 is counted too once it lands).validate_expressiontakessweepObject(optional; siteflow_conditionwithtriggerKind: 'time_relative'only, a tool error anywhere else, the same refusal shape asseverity/triggerKind). It is the flow'stimeRelative.object; another object thanobjectNamegives the gate's warning instead of the run-fails error. Theflow_conditionoutcome reads a third input,sweepsTypedObject, the lint ledger's own third fact. The tool description and the MCP skill text (skill-md.ts) name the input.tr_defaultpin (a descriptor with noobject, a shape that cannot bind) is re-purposed totr_sweep_only: a start with noobjectNamesweepingcrm_opportunity, refused at start, decision and edge. New pins: the note ontr_other(once, itswhere,pathandsource) and its absence on a cleantr_other, ontr_sameand ontr_sweep_only; a well-typedtr_sweep_onlyflow is clean. The MCP per-trigger-kind table gains three rows (sweepObject=objectName, start with noobjectName,sweepObjectanother object), each asserted equal to the gate's own verdict on the same flow, and the wrong-site refusal list gains foursweepObjectcalls. The key-read meta-test now listsidonstartNode(the note names the start node)..changeset/30026-time-relative-sweep-typing.md,minoron@objectstack/lintand@objectstack/mcp, BREAKING with FROM → TO, ADR-0087already-registered flow-record-read-and-decision-type-mismatch-refused, and a correction note for the released fix(lint)!: a flow condition's text-vs-number comparison gets a true verdict on every flow, record.x read and decision branch #30015 changeset text ("or is omitted"), which is left as is.Acceptance notes
already-registered flow-record-read-and-decision-type-mismatch-refused: the entry prescribes this exact rewrite for a time-relative flow's conditions (same code, same replacement, same reason). Itssurfacetext says "sweeping the start node's objectName", which this change makes narrower than the gate; editing it ispackages/spec/**, outside this card's surface. The seat files it fordomain:spec.record.FIELDread of a field only the swept object declares is still refused as unknown there, while the run would hold it. That is pre-existing and not made worse here; the new note tells the author to align the two objects, which removes it.deriveTriggerBinding(plugin-extension-api.ts) still namesconfig.objectNameas the binding'sobjectwhentimeRelative.objectis absent. The sweep itself reads only the parsed descriptor, so no run depends on it; noted, not touched.Verification
All readings on head
bc376239(the two commits merged withmainat797f9257) unless named.Build, typecheck, tests
pnpm --filter '@objectstack/lint^...' --filter '@objectstack/mcp^...' build, VERDICT command-exit 0; fullturbo run build --filter='!@objectstack/docs', 119/119 tasks, VERDICT command-exit 0.@objectstack/lint:pnpm test7996/7996 (193 files);pnpm typecheck0 (withcheck:test-typecheck).@objectstack/mcp:pnpm test678/678 (56 files);pnpm typecheck0 (withcheck:test-typecheck).eslint --no-inline-configover the five changed source and test files: 0. The repo-widepnpm lintis CI's.Ablations (each through scripts/ablation-replace.mjs; restored blob == HEAD, `git diff HEAD` empty)
src):objectName = startObject ?? sweepObject→startObject. 1 red:tr_sweep_only: expected [] to deeply equal [ …3 ], the pre-fix answer (no verdict at all).dist/(the MCP suite reads@objectstack/lintthrough its exports): the same mutation with a string marker, lint rebuilt,ablation-dist-preflightpresent in 4 built files; 1 red:time_relative, start names no objectName: expected [] to have a length of 1. Restore leg rebuilt, preflight--absentover all 20 built files, suite 38/38.sweepsTypedObjectforcedtrue. 1 red:sweepObject another object: expected [ [ 'error', … ] ] to deeply equal [ [ 'warning', … ] ].Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackonbc376239: 59 families; all 59 run, each exit 0 (exit code captured per line before any pipe).--ranreconciliation: "59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED".Dogfood (the narrowing arm's battery, on the final head)
pnpm --filter @objectstack/dogfood testunder the lock, no shard, no path: Test Files 418/422 passed (3 skipped, 1 failed), Tests 3491 passed, 36 skipped, 1 failed; VERDICT command-exit 1.permission-set-clone-door.dogfood.test.ts› "a platform set … copies into its shipping package's pair": cloningmember_defaultanswers 422,objects.sys_comment/objects.sys_activitynot defined in the target package. It is not this change: withvalidate-expressions.ts,mcp-http-tools.tsandskill-md.tsset back toorigin/mainand lint and mcp rebuilt, the same file is red the same way (1 failed / 10 passed). The grants arrived onmainwith fix(plugin-security): every member reads and posts comments, and reads the activity history, of the records they can read #30222 (490d136f, "every member reads and posts comments, and reads the activity history"), inside this branch's base.🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6CaUg3ibF939L2RFB5D
Generated by Claude Code