feat(objectql,spec,client,metadata-protocol)!: #4286 收官 — having 落地执行,cursor/distinct 连同 SDK 生产者移除 - #4307
Merged
Conversation
…gine applies it after aggregation (#4286 step 3) ADR-0049 resolved to ENFORCE: having was the one declared member every SQL-literate author expects to work next to groupBy/aggregations, and its gap was structural (finding 1 — aggregate() rebuilt the driver AST without it, and the findData aggregate branch dropped it on the wire). - applyHaving() (objectql/src/having-filter.ts) runs AFTER aggregation on the native-driver path and the in-memory fallback alike; namespace is the aggregated row's own columns (aggregation aliases + groupBy projections). An unknown operator rejects loudly — ignoring one would silently return unfiltered aggregates, the ADR-0078 failure enforcement exists to end. - EngineAggregateOptionsSchema declares having; findData's aggregate branch forwards it; the ast carries it so the FLS predicate guard (which already walked having references) sees caller input. - Native SQL HAVING pushdown can come later behind a driver capability flag (the dateGranularity two-tier pattern) without changing semantics. - Ledger: query.having flips dead → live with end-to-end evidence; docs and the objectstack-query skill stop teaching the app-code post-filter workaround. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012K2BX3WQwPBA2ZBehqJUBX
…ry.distinct with their SDK producers (#4286 step 4) ADR-0049 resolved both to REMOVE. Neither ever had an executor: - cursor promised keyset pagination no driver implemented — accepted and ignored, every page identical, so a caller looping "until hasMore" never terminated. Tombstoned on QuerySchema and EngineQueryOptionsSchema (one shared prescription); QueryBuilder.cursor() deleted. The manual keyset (a where predicate on the sort key) is the documented pattern; a first-class cursor, if ever designed, will be a response-minted opaque token. The cursor params on listRevisions/flow-runs/notifications are those endpoints' own live tokens — untouched. - distinct was MIS-WIRED, not merely dead (finding 2): its only observable effect was suppressing the REST list count, so callers got duplicate rows AND degraded total/hasMore — a side effect that "confirmed" a capability that never ran. Tombstoned on both schemas plus the ?distinct querystring spelling (HttpFindQueryParamsSchema); QueryBuilder.distinct() deleted; the countable suppression branch deleted — total is truthful again (the observable REST change, carried in the changeset). AggregationNode.distinct (per-aggregation dedupe) is a different, live member and stays. Both register as protocol-18 semantic migrations (query-cursor-retired, query-distinct-retired) — request shapes, nothing stored to rewrite. Ledger entries flip to REMOVED notes (rows stay: retiredKey keeps each key in the walked shape); docs and the objectstack-query skill teach the live spellings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012K2BX3WQwPBA2ZBehqJUBX
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ng the removed cursor key (#4286) Found via the docs-drift advisory on #4307: the 'Query is slow' checklist still demonstrated cursor: { id: lastSeenId } — the key the same PR tombstones. The example now expresses the keyset as a where predicate on the sort key, matching the pattern every other page teaches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012K2BX3WQwPBA2ZBehqJUBX
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.
按裁决执行 #4286 第 3、4 步(前置分析见 issue 讨论;第 1/2/5 步已由 #4294 合并)。趁 17 发布窗口,三项破坏性收拢在同一未发布 major。两个 commit,可分开审:
Commit 1 —
havingENFORCE(6c0b13d,非破坏)裁决理由:
groupBy+aggregations+having是模型先验里的固定搭配——enforce 是唯一让先验与平台一致的选项。落地:applyHaving()(objectql/src/having-filter.ts)在聚合之后统一应用,原生驱动路径与内存兜底路径完全一致——沿用dateGranularity的 correct-first / optimize-later 两层模式,将来 SQL 原生HAVING下推挂驱动能力旗即可,语义不变。$and/$or/$not。EngineAggregateOptionsSchema声明having;findData聚合分支转发;ast携带它使 FLS 谓词卫兵(早已遍历having)看得到调用者输入——上一个 PR 特意保留那段遍历,就是为今天。having的查询是静默返回全部分组,现在按字面过滤——这就是 enforcement 本身。Commit 2 —
cursor/distinctREMOVE(9484d1a,破坏性)cursor:无任何驱动实现 keyset,游标被接受并忽略——每页都是第一页,while (hasMore)永不终止。QuerySchema + EngineQueryOptionsSchema 双点墓碑(共享处方),QueryBuilder.cursor()删除。处方指向手动 keyset(sort key 上的where谓词,文档既有正解);将来真做 first-class cursor 应是响应侧铸造的不透明 token,与今天这个调用者拼的 record 是不同 API——保留只会保存一个错误设计。listRevisions/flow-runs/notifications 的cursor: string参数是那些端点自己的活 token 分页,未动(ledger 注记已写明)。distinct:比死键更糟的 mis-wired(finding 2)——唯一可观测效果是 REST 列表 count 抑制:调用者拿到重复行 + 退化的total/hasMore,而这个副作用还会让自验证的 agent 误信旗标生效。三点墓碑(QuerySchema / EngineQueryOptionsSchema /?distinctquerystring 拼写HttpFindQueryParamsSchema),QueryBuilder.distinct()删除,count 抑制分支一并删除——受影响查询的total恢复真实(可观测 REST 变化,changeset 已明示)。每聚合项AggregationNode.distinct(活)不受影响。query-cursor-retired/query-distinct-retired)——请求面,无源可改写,[P3] data:FieldNode's nested-select object form is declared but nothing produces or consumes it — enforce or remove #4196 先例。Ledger 收官
liveness/query.json:having翻 live(端到端证据),cursor/distinct翻 REMOVED 注记(墓碑使键留在 walked shape,行保留)。闸门现报query 27 classified (live 16, experimental 7, dead 4)——dead 4 全部是已墓碑化的移除项,#4286 的 enforce-or-remove 工单在请求面上闭环,issue 可关。验证
?distinctquerystring 拒绝、EngineQueryOptions 双拒)。tsc --noEmit干净;spec 14 个闸门全 PASS;check:generated产物按裁定窄向重生成(api-surface 新增两个导出的处方常量;authorable-surface 三键翻[RETIRED]、EngineAggregateOptions:having新增)。objectstack-queryskill 同步:HAVING 从「protocol only/后置过滤 workaround」翻为已执行语义;keyset 与 distinct 小节改为移除记录 + 活拼写。🤖 Generated with Claude Code
https://claude.ai/code/session_012K2BX3WQwPBA2ZBehqJUBX
Generated by Claude Code