fix(docs,test): 文档失真修正与 ReDoS 断言去负载敏感 (#4485, #4476, #4486, #4452) - #4497
Merged
Conversation
The ReDoS assertion in protocol-handshake.test.ts bounded the pathological scan with an absolute 50ms wall clock. Under the full-repo run (~130 parallel turbo tasks) that ceiling measures machine load rather than the parser: it exceeded 50ms on a healthy tree and reddened PRs that never touched this package, leaving the diagnosis cost to whoever happened to be running. The underlying guard is real and stays (CodeQL 837/838). What changes is how it is measured. The three `toBeNull()` behavioural assertions -- the actual contract, that adversarial input is unrecognized rather than falsely rejected -- are kept and now stand on their own. The wall-clock proxy is replaced by a scaling check: the same adversarial shapes at 1x and 8x length, asserting the parse stays linear in the input. Load largely cancels out of a ratio, which is what makes the criterion load-insensitive. Measured: healthy parsing tracks the input at 8.3-8.5x, stable across runs. The 40x ceiling keeps ~5x headroom while still catching a merely quadratic regression (~64x), let alone an exponential one, which would not finish. Two measurement details are load-hardening, both established empirically: timings are taken back-to-back within one iteration and reduced by minimum *ratio* rather than minimising each timing independently (a scheduler steal landing in only one window skewed the latter, observed reddening at 3x CPU oversubscription); and the JIT is warmed so the baseline is not inflated. Note the pathological-to-benign ratio suggested on the issue does not work here: a benign 16-char range parses ~300x faster than a 100k-char one purely because it is 100k characters shorter, so it would fail on a healthy machine. Verification: 5/5 consecutive clean runs, plus 8/8 under 3x CPU oversubscription (the shape that reproduced the original failure). Fixes #4485
Seventeen passages dated v17 removals to `@objectstack/spec` 18. They ship in
17 -- this train. The number is the actionable half of a removal notice: a
reader on 16 asking whether upgrading to 17 breaks their cursor-paginated loop
was told the removal is a major away, so they plan for it later and the
upgrade breaks.
Evidence that 17 is correct: `spec-changes.json` carries `toMajor: 17` for
data.query.{cursor,distinct,joins,windowFunctions} and stack.api.requireAuth;
this tree is 17.0.0-rc.1 with `PROTOCOL_VERSION = '17.0.0'`; and the keys are
already `[RETIRED]` in `authorable-surface.json`. A removal cannot be retired
in a 17 build and also ship in 18.
#4476 fingerprinted nine locations. Grepping the bare pattern -- which the
issue itself recommended over working the list file-by-file -- found eight
more:
- The nine listed: query-syntax.mdx (4), queries.mdx (4), troubleshooting.mdx.
- query-syntax.mdx:98, the #4286 sweep summary paragraph, same error.
- skills/objectstack-query/ (5): SKILL.md and the aggregation/pagination rules.
These are agent-facing and the highest-leverage of the set -- an agent
authoring a query reads them as ground truth. Body prose only, so the
frontmatter-derived listings that build-skill-docs.ts generates are unchanged.
- implementation-status.mdx (2): the same error shape on a different change,
`api.requireAuth` (#3963), which spec-changes.json also puts at toMajor 17.
Also records the fingerprint in the sweep run log, as #4476 asks. Runs 1-2
matched on surface names and so read past passages that named the right
surface and the wrong release; the new row tells the next run to check the
number, with spec-changes.json `toMajor` as the arbiter.
Fixes #4476
The `IDataEngine` block in data-engine.mdx wrote all four read methods with two parameters, dropping the trailing `options?: BaseEngineOptions` that the real contract gives each of them (packages/spec/src/contracts/data-engine.ts :70/85/89/90). The write methods in the same block each carried their own `options`, so the block taught exactly the wrong model -- "writes take options, reads do not" -- and that is the misconception #4251 existed to fix. The parameter is not incidental: the same `{ context }` object is correct as insert's 3rd argument but was SILENTLY DROPPED as find's, so an intended `isSystem` bypass vanished and control-plane reads came back empty once org-scoping hooks landed. Anyone -- human or agent -- writing code from this block was being led back to the pre-#4251 shape, against a failure mode that raises no error. Adds `BaseEngineOptions` to the block's import list (the contract imports it from the same module), and a callout recording the precedence the contract states: `query.context` remains supported, and when both are given `options.context` wins. `content/docs/kernel/contracts/` is hand-written -- only `content/docs/ references/` is generated -- so no generator run is involved. Fixes #4486
#4452) The README's flow sections described a DSL that has never existed. Every node type name was wrong (`record_create` vs `create_record`, `query` vs `get_record`), the interpolation dialect was Salesforce's `{!…}` which the platform does not parse, and branching/looping/error handling were written as nested `steps` arrays -- a shape the schema has no key for. Nothing in it ran. Since #4414 + #4439 this stopped being merely useless: `conditions[].expression` is on the expression ledger, so the README's `'{!trigger.record.amount} > 10000'` is now REFUSED at `registerFlow()` / `objectstack validate`. An author copying it got a CEL error whose advice ("drop the braces") did not fix their node, because the node's entire shape was wrong too. Rewritten from the schemas and executors, not from the old README: - Flows are a DAG of flat `nodes` + `edges`. Branching is an edge, and a decision routes by matching its branch `label` to an out-edge `label` -- the #4414 trap, called out inline. - The record-change binding lives on the `start` node's config (`{ objectName, triggerType, condition }`), not at the flow top level. - CRUD config table: `objectName` (not `object`), `filter` as an OBJECT (not a `filters` array of triples), `outputVariable` (not `output`), no `recordId`. Notes that `update_record` has no `outputVariable` -- the executor reads none. - Both expression dialects stated with the rule that disambiguates them: every condition is bare CEL, braces are for values. Says plainly that `{!…}` is not a dialect here. - `loop` / `parallel` / `try_catch` given their real ADR-0031 region shape (`config.body`, `config.branches`, `config.try`/`catch`), and `wait` its node-level `waitEventConfig` block rather than the invented `duration` + `nextSteps`. - Flow `type` values corrected (`schedule`, not `scheduled`). Per the issue's preference, the per-node reference is NOT duplicated here: the README now points at content/docs/automation/flows.mdx, the maintained one. Keeping a second hand-written node catalog in a package README is the #4027/#3569 shape that produced this drift. `pnpm --filter @objectstack/spec check:generated`: all 8 artifacts up to date. Fixes #4452
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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:
|
…st fixes The Check Changeset gate requires every PR to add at least one `.changeset/*.md` relative to base. This branch touches only `.md`/`.mdx` prose and one `.test.ts` file -- verified against the diff, no package source, no public export, no protocol change -- so the empty-frontmatter form is the accurate declaration: it publishes nothing.
…oo (#4452) The Node Types section presented `FLOW_BUILTIN_NODE_TYPES` (i.e. the `FlowNodeAction` enum) as the built-in set, but the ADR-0031 structured constructs `parallel` and `try_catch` ship registered builtin executors without appearing in that enum -- which is exactly why `FlowNodeSchema.type` validates against the live action registry rather than a closed enum. Left as written, the list contradicted the Advanced Features section just below it.
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.
v17 验收缺陷清单 #4482 的文档与 CI 稳定性批次。四项全部落地,CI 全绿。
已落地
#4485—— ReDoS 断言去负载敏感(建议优先合并)原断言用绝对 50ms 墙钟给病态输入设上界。这个上界量的是机器负载而不是解析器:全仓
pnpm test(约 130 个 turbo task 并行)下,健康代码也会超过 50ms,把完全没碰@objectstack/metadata-core的 PR 染红,诊断成本由当时正好在跑的人承担。三条
toBeNull()行为断言(真正的契约:病态输入是"无法识别"而非误拒)全部保留。被替换的只是那条墙钟代理指标,改为规模判据:同样的病态形状分别在 1x 和 8x 长度上测,断言解析开销随输入长度保持线性。负载在比值里基本抵消,这才是"对负载不敏感"的来源。实测:健康解析随输入呈 8.3–8.5x,跨次稳定。40x 的上限留了约 5x 余量,同时仍能抓住哪怕只是二次的回归(约 64x),指数级回溯更是根本跑不完。
两处加固都是实测得出的,不是拍脑袋:
#4476—— 版本号从 18 改回 17(实际覆盖 17 处,多于 issue 列的 9 处)issue 列了 9 处,按它自己的建议改用裸模式全仓 grep,实际找到 17 处:
protocol/objectql/query-syntax.mdxdata-modeling/queries.mdxdeployment/troubleshooting.mdxskills/objectstack-query/(SKILL.md3 +rules/aggregation.md1 +rules/pagination.md1)releases/implementation-status.mdxapi.requireAuth(#3963)判定 17 正确的证据(机器可读,非推断):
spec-changes.json中data.query.{cursor,distinct,joins,windowFunctions}与stack.api.requireAuth全部为toMajor: 17;本树为17.0.0-rc.1/PROTOCOL_VERSION = '17.0.0',且这些键在authorable-surface.json里已是[RETIRED]—— 一个变更不可能既在 17 的构建里已退役、又要到 18 才发布。skills 只动正文、未动 frontmatter,因此
build-skill-docs.ts从 frontmatter 派生的清单不受影响(check:skill-docs/check:skill-refs已验证)。按 issue 要求,把这个指纹补进了
docs/v17-docs-sweep.md:前两轮 sweep 只匹配接口名,所以径直读过了"接口名对、版本号错"的段落;新增行要求下一轮核对数字,并以spec-changes.json的toMajor为准。#4486—— IDataEngine 读方法补回尾部options四个读方法都少了
options?: BaseEngineOptions。同一代码块里写方法各自带着options,所以它教出来的恰好是反的心智模型("写有 options、读没有")—— 而这正是 #4251 要修的那个误解。这个尾参不是可有可无:同一个
{ context }作为insert第 3 参正确,作为find第 3 参却被静默丢弃,于是本意为isSystem的绕过凭空消失(org-scoping hook 落地后控制面读取开始返回空)。照着这段文档写代码的人或 agent,会被引回 pre-#4251 的写法,而这个失败模式不报错。同时给代码块的 import 补上
BaseEngineOptions(真实契约从同一模块导入),并按契约原文记下优先级:query.context仍受支持,两者同时给出时options.context胜。#4452—— service-automation README 按真实 flow DSL 重写原 README 描述的 DSL 从未存在过:节点类型名全错(
record_createvscreate_record、queryvsget_record),插值方言是 Salesforce 的{!…}(平台从不解析),分支/循环/错误处理写成嵌套steps数组(schema 里没有这个键)。照抄没有一个节点能跑。#4414 + #4439 之后这不再只是"没用":
conditions[].expression进了表达式账本,README 的'{!trigger.record.amount} > 10000'现在会在registerFlow()/objectstack validate直接被拒 —— 而报错给的建议("去掉花括号")修不好,因为整个节点形状也是错的。按 schema 和 executor 重写(不是照着错的版本改):
nodes+edges的 DAG。分支是边,且 decision 靠把分支label与出边label对上来路由 —— A decision node has three declared ways to route a branch and two of them do nothing — app-crm's convert-lead guard runs both branches #4414 那个坑,已在示例里就地标注。start节点的 config({ objectName, triggerType, condition }),不在 flow 顶层。objectName(非object)、filter是对象(非filters三元组数组)、outputVariable(非output)、没有recordId。并注明update_record没有outputVariable—— executor 不读。{!…}不是这里的方言。loop/parallel/try_catch给出真实的 ADR-0031 region 形状(config.body、config.branches、config.try/catch),wait用节点级waitEventConfig而非臆造的duration+nextSteps。type值修正(schedule,非scheduled)。parallel/try_catch虽不在FlowNodeAction枚举里,但确有注册的内建 executor —— 这正是FlowNodeSchema.type走活注册表而非闭合枚举校验的原因。按 issue 的倾向,没有在这里复制一份逐节点参考:README 现在指向
content/docs/automation/flows.mdx(在维护的那份)。在包内 README 再维护一套节点清单,正是 #4027/#3569 那个"两份手写清单不对账"的形状,也就是这次漂移的来源。验证
eab5083,全部 5 个 workflow)#4485测试文件连跑#4485负载压测@objectstack/metadata-core全量测试pnpm --filter @objectstack/spec check:generatedpnpm typecheck(全仓)#4476九处逐行核对changeset
本分支只动
.md/.mdx与一个.test.ts(已对 diff 核实:无包源码、无公开导出、无协议变更),因此用空 frontmatter 的 release-nothing changeset —— 它是"本 PR 不发布任何东西"的正式声明,而不是漏填。是否可转正式
可以转正式评审。 四项均已完成、CI 全绿、验收标准(#4485 连跑)已满足。我按纪律没有自行转正式,也没有开 auto-merge,请按需翻转。
其中
#4485值得优先合并:它落地后能减少其他 PR 因那条 50ms 墙钟断言而无端染红,属于对并行开发的即时止血。需要评审者留意的范围判断
有两处我超出了 issue 的字面清单,均属"同型文档失真",若认为应拆分请指出:
skills/objectstack-query/的 5 处 —— docs: nine passages date the #4286 query-surface removals to spec 18 — they ship in 17 #4476 的指纹清单未覆盖,但 issue 明确建议"用裸模式 grep 而不是逐文件改"。implementation-status.mdx的 2 处 —— 同型错误,但属于api.requireAuth(把 public 从"全局开关的副产品"升级为声明式能力,然后删掉 api.requireAuth 开关 #3963)而非 [P2] data:QueryASTdeclares 12 members no executor runs — the liveness ledger governs metadata types, not the request surface #4286;spec-changes.json同样给出toMajor: 17。未触及任何可作者化 spec key、公开导出或协议面,因此没有需要拍板的协议问题。
分支:
claude/v17-verification-defects-gnf9e6-docsFixes #4485
Fixes #4476
Fixes #4486
Fixes #4452