Repository navigation
fix(lint): warn on a flow start input declared a record of the object-less placeholder, or one id the flow reads as a list - #30753
Conversation
…-less placeholder, or one id the flow reads as a list Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
… warnings Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
…start doors enforce the declaration Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
📓 Docs Drift CheckThis PR changes 1 package(s): 23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 4 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 a08fec2c639db674dade37655b32c6f1738e1a22 && git checkout a08fec2c639db674dade37655b32c6f1738e1a22
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3098661cd3ad78634dd77a31ae68530ab9a3a83 922bc3dd66cce58dc28d81dd512c4f651f6ff610 && git checkout -B drift-repro b3098661cd3ad78634dd77a31ae68530ab9a3a83 && git merge --no-ff 922bc3dd66cce58dc28d81dd512c4f651f6ff610
node scripts/docs-audit/affected-docs.mjs --json b3098661cd3ad78634dd77a31ae68530ab9a3a83
|
…oors enforce the declaration Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
…tNodeOf's docblock back above it Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytLmJxfAmgPVjUbbW
|
domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW Contract reviewServed-tier: The face is the declaration alone: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix (non-blocking): Implemented-by: VERDICT: PASS |
|
domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW 席位核对 · ACCEPT · 2026-11-06T02:15Z · #30633Head-sha: 922bc3d
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 42149417291 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 42150976034 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW 席位核对 · Queue failure triage · 2026-11-06T03:03Z · #30633Both queue failures (builds 42149417291 and 42150976034) are the same case: Test Core (4/8),
No fix exists yet; filed as #30789 (mask each source once, or reuse the shared masker; not a raised timeout). This seat re-queues this PR once more. If it fails a third time on the same case, it waits for #30789 instead of burning the queue again. |
Fixes #30633
Clause-②: yes (widening) —
@objectstack/lintexports two new rule-id constants,FLOW_INPUT_OBJECT_LESSandFLOW_INPUT_RECORD_READ_AS_LIST; nothing that passed before is refused.Metadata-protocol: none
维护者速读
os validate的流程触发就绪检查新增两条警告——开始输入把objectName写成无对象占位符'global';或声明为单条记录 id、却被同一流程的节点按列表(filter.id.$in)读取。flow-input-unknown-object判定不变。回滚即还原本 PR。packages/lint/src/validate-flow-trigger-readiness.ts的 1j 段(第二条警告的读取范围是否与运行时准入一致),其次同名测试文件新增的 describe;index.ts只加两个导出名。pnpm --filter @objectstack/lint exec vitest run src/validate-flow-trigger-readiness.test.ts,预期 141 个测试全部通过(整包 8183 个)。What changes
Two new advisory ids from
validateFlowTriggerReadiness, besideflow-input-unknown-object:flow-input-object-less: a start input declaresobjectName: 'global'(or'*', which only a pre-parse stack can carry). The start doors' load skips an object-less key:loadActionSubjectRecordinpackages/runtime/src/action-execution.tsloads only when!isObjectLessActionKey(objectName), and the declared read that PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628 added (flowDeclaredRecordReads→refuseUnreadableFlowStartRecords) goes through that load. So the value reaches the run unjudged. Located atflows[N].variables[M].objectName.flow-input-record-read-as-list: a start input declares one record id, and aget_record/update_record/delete_recordnode in the same flow reads it as a list (filter.id.$inhanding the input on as it is). Admission judges the input once per reading:oneRecordIdOrRefusefor the declaration andrecordIdListOrRefusefor the$inread. No value passes both, so every start that supplies the input is refused400. Located at the node'sconfig.filter.id.$in, nested regions included (walkFlowNodes).The second rule reads the flow the way
flowDeclaredRecordReadsdoes: onlyisInput: truevariables, only a CRUD node with a literalobjectName, only a CEL envelope whose whole source is the input (bare orvars.), and not where aloopormaprebinds the name. Both ids arewarningin every context, a stated package closure included.Also in this PR (claim amended to admit them):
packages/lint/src/index.tsre-exports the two new constants, becauserule-id-barrel-exports.test.tsrequires every rule id to be reachable from a published barrel.flow-input-unknown-objectmessage, theFLOW_INPUT_UNKNOWN_OBJECTdocblock and the 1i comment now say the start doors enforce the declaration (PR fix(runtime): a flow start input declared a record id is loaded in the caller's own scope from the declared object at every door that starts the flow by name #30628), not that they will. The message changes from "…every start that supplies the input will be refused once the start doors enforce the declaration." to "…every start that supplies the input is refused." No test pinned the old sentence. The id, severity, path, hint and closure verdict are unchanged.packages/lint/src/rule-explanations.ts(theflow-input-unknown-objectparagraph, admitted by a second claim amendment): "The declaration is planned: no door reads it yet" now reads "Every door that starts the flow by name enforces the declaration", and the paragraph's "are to load" / "would refuse" are now present tense.Tests
New top-level
describeinvalidate-flow-trigger-readiness.test.ts, plus one row per id in the severity table:whereand path on'global', with the existingflow-input-unknown-objectfinding still beside it;'global'defined as a stack object still warns;'*'; a silent control on a defined object.nodes[1].config.filter.id.$in; avars.hand-off inside atry_catchregion, located at the nested node; seven silent controls (filter.id,$eq, undeclared input, non-input, computed source, loop rebinding,create_record).The existing
flow-input-unknown-objectcases are unchanged.Acceptance notes
'global'on a stack that defines no object of that name,flow-input-unknown-objectstill fires beside the new id. The card asks that the existing cases stay as they are, and under a stated closure that id is a publish refusal, so replacing it would widen the gate.subflowormaphand-off into a callee flow (admission follows those up to 8 levels deep; this rule judges the same flow only). Amapover the input whose callee reads each item as one id also reads the input as a list, and is not judged here either.os explainentry. Each finding carries its full verdict and fix.@objectstack/speconly. It has to be kept in step withisObjectLessActionKeyby hand, asstack.zod.ts'sGLOBAL_ACTION_SCOPEalready is.Verification
All readings are at head
922bc3dd6.Lint package: tests, typecheck, eslint
pnpm --filter @objectstack/lint test: exit 0. Test Files 195 passed (195), Tests 8183 passed (8183).pnpm --filter @objectstack/lint typecheck(tsc --noEmit && pnpm check:test-typecheck): exit 0.pnpm lint(eslint, repo-wide): exit 0.Derived gate families: 57 commands
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 57 commands from 5 paths, merge base 06cccd3.check:type-check-debt,check:dual-build-cjs-loadsandcheck:lean-entry-closureincluded.--ranreconciliation: "57 derived, 57 run, 0 NOT-MEASURED, 0 UNRUN", exit 0.Ablations (scripts/ablation-replace.mjs, each restored to the HEAD blob)
Taken at 989f345. The guarded lines are byte-identical at 922bc3d: the later commits touched only the barrel, wording, the changeset and the position of one existing docblock.
false &&: 4 failed, 137 passed (the severity row and the three object-less cases).continue: 3 failed, 138 passed (the severity row, the exact-finding case, the nested-region case). The first attempt used the replacement textcontinue;, which already occurs 4 times in the file, so the tool could not prove the mutation landed and refused it; nothing ran. It was re-run with a unique marker.Generated by Claude Code