Filed by the domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T06:40Z, out of the at-ACCEPT residue of PR #17257 (#15442 / #15449). ⛔ Unclaimed. No domain:* label and no pm:* state: ⛔ both are the triage seat's to produce. This is a decision card by shape — the four-facet block below is filled in at filing time as the protocol requires; ⛔ the seat does not adjudicate it.
Measured on origin/main 501959b72.
One sentence
A customer who saved a page last month with a filter written the old way will find it still renders exactly as before — right up until someone opens that page in the builder and hits Save, at which point the platform refuses the save and hands back a message that does not say how to fix it, and there is no tool that will fix it for them.
Background
Seven filter doors converged on the rule array [{ field, operator, value }, ...] (objectui#6206, maintainer batch adjudication 2026-08-25, verbatim 「同意」, Option B; family-wide convergence, decision batch #55, 2026-09-06, verbatim 「同意」, option A). The shipped disposition is deliberate and is written into the element-data-source-and-object-block-filter-rule-array entry in its own words:
⚠️ Metadata AT REST is deliberately NOT rewritten, and this disposition adds no D2 conversion — a SemanticMigration converts nothing by its own type, and os migrate meta --stored (the pass over a deployment's sys_metadata rows) replays D2 conversions only, so it has nothing to rewrite for this shape. The read path does not re-validate stored rows (applyConversionsToStoredItem replays the full chain without validating, by its own contract), so a stored page or block carrying the record form keeps loading unchanged and is still rendered by objectui at the pinned .objectui-sha; what changes is that RE-SAVING it is refused at the filter door, on its next save and not before.
So today: read = fine, re-save = refused, no conversion, no rewriter.
The question the convergence left open
The three entries' own replacement text already spells out a total, mechanical mapping for the common shapes:
- flat record —
{ status: 'active' } → [{ field: 'status', operator: 'equals', value: 'active' }]
- operator object —
{ amount: { $gt: 100 } } → [{ field: 'amount', operator: 'greater_than', value: 100 }]
- several keys → several rules (they AND)
- ObjectQL AST tuple array —
[['owner_id', '=', '{current_user_id}']] → [{ field: 'owner_id', operator: 'equals', value: '{current_user_id}' }]
- legacy operator shorthands (
eq, ne, gt, notIn, …) accepted and normalized on parse
That mapping is losslessly executable by a machine for exactly that subset. What it is not total over is $and / $or / $not — the record's combinator keys — because the rule array is a flat AND-list with no nesting.
⚠️ The one measured fact that decides how much is at stake: the record's $and / $or / $not keys "were misread by every gate block anyway (objectui#6948)" — that is the shipped entry's own words, and objectui#6948 is closed. So the nesting the rule array cannot express is nesting the consumer never honoured. That makes the un-mappable remainder small, but it is exactly the part where a mechanical conversion would have to either refuse or change behaviour.
Governing text
- ADR-0087 §D2 (a real conversion, replayed by
os migrate meta --stored) vs §D3 (SemanticMigration — prose, converts nothing; packages/spec/src/migrations/types.ts).
- ADR-0087 at its own
not-required (no-migration-prescription) clause refuses that disposition when the entry body carries a prescription. These three entries carry a full prescription, so D3 is the floor, not not-required.
packages/spec/src/conversions/stored.ts — applyConversionsToStoredItem replays the chain without validating, which is why stored rows keep loading.
Restart-touch: packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.ts
Re-check before acting: git log --oneline -20 origin/main -- packages/spec/src/migrations/entries/semantic/ and git grep -n "'element-data-source-and-object-block-filter-rule-array'" origin/main -- packages/spec/src/migrations/registry.ts — if a D2 has been added since, this card is already answered.
Options and what each actually costs
|
what gets built |
what the customer sees |
| A — leave it (the shipped disposition) |
nothing |
Old pages keep rendering. The first person to re-save one gets a refusal. Whoever that is has to hand-rewrite the filter, with the current bare invalid_type as their only guidance (that message gap is a separate card). Nothing tells them how many such pages a tenant has. |
| B — partial D2: convert the mappable subset, leave combinators alone |
a real D2 conversion covering flat records, {k: {$op}} objects and single-level AST tuples; a combinator-carrying record is passed through untouched |
os migrate meta --stored cleans up the great majority of stored rows in one operator-run pass; those pages re-save cleanly afterwards. A tenant with a $and/$or filter is left exactly where option A leaves everyone — but that is a much smaller population, and (measured) that nesting was never honoured downstream anyway. Cost: a conversion that is deliberately partial is a shape this ledger has not shipped before, so "did it convert my row or not" becomes a question an operator can ask and today cannot answer. |
| C — full D2, converting combinators by flattening them |
as B, plus $and flattened into the AND-list and $or / $not handled by some chosen rule |
Every row converts; nothing is left behind. But $or has no representation in a flat AND-list, so this option necessarily changes what some pages select — silently, during an operator-run migration, on data. That is the failure mode this platform's rules exist to prevent. |
Business translation
- A = "we changed the lock and did not issue new keys; whoever tries the door next finds out."
- B = "we re-key every door we can re-key mechanically, and put a note on the handful we cannot."
- C = "we re-key every door, and accept that a few of them now open onto a different room."
os-decision-facets
- ① 项目长远合理性:B 缩小特例 —— 它把「旧写法怎么办」从每个客户各自的意外,收敛成一次可运维的批处理加一份点名清单;A 把这个特例永久留在客户侧,每张老页面都是一次将来的意外;C 表面上最干净,但它引入的是「迁移会悄悄改变筛选结果」这条永久债务,比它清掉的特例贵得多。
- ② 实际业务拉动:今天撞上的人 = 任何一个打开旧页面按保存的人。⚠️ 拉动的真实大小本席测不到 —— 那是各部署
sys_metadata 里的存量,不在仓里;仓内作者面已在同一个改动里全部改写(实测:绑定处 4 个 spec fixture、block 门 5 个 showcase 作者、3 个 lint fixture,仓外为零)。⇒ 若维护者判断线上存量接近零,零拉动的默认动作是 A。
- ③ 防 AI 犯错:A 的失败是响亮的(保存被拒),这是它最强的一面;它的弱点是拒绝话术不说怎么改(另卡)。B 同样响亮,且把响亮的次数降到最低。⚠️ C 的失败是静默的 —— 迁移跑完不报错,页面照常打开,只是选出来的行变了。⇒ 单凭这一轴,C 应当出局。
- ④ 创业阶段不扩散:A 不新增任何长期义务;B 新增一个必须长期正确的转换器,但它转换的是一个已经冻结的旧形状(接受集已经关了),所以这份义务是有终点的、可删除的;C 新增的是一份永远说不清的义务。⇒ 不扩散这一轴上 A > B ≫ C。
推荐:B(部分 D2),回退项 A。理由:它是唯一一个既降低客户侧意外、又不把静默行为改变引进迁移路径的选项;而它新增的义务有明确终点。⚠️ 置信缺口 —— 本分析看不见的东西:线上部署 sys_metadata 里究竟有多少行携带旧记录形筛选,以及其中多少携带 $and/$or/$not。这个数一旦接近零,推荐立刻翻成 A;它只能由维护者或一次面向部署的读数回答,⛔ 仓内测不出来。
维护者速读
老页面里用旧写法存下来的筛选条件,现在读没问题、存会被拒,而且平台不提供任何自动修复。绝大部分旧写法可以被机器一对一改写成新写法;只有带「或者 / 并且 / 非」这种嵌套组合的那一小撮不行 —— 而实测表明,那种嵌套前端从来就没真正执行过。
- A:什么都不做,谁碰到谁自己改。
- B:写一个只改能改的那部分的转换器,运维跑一次批处理清掉绝大多数,剩下那一小撮点名列出来。(推荐)
- C:全都自动改,包括嵌套那一撮 —— 代价是有些页面迁移后筛出来的数据会悄悄变。
⚠️ 决定这件事划不划算的唯一数字,是线上到底有多少张老页面带旧写法的筛选 —— 这个数只有你看得到。若接近零,选 A。
请回一个字母:A / B / C。
Related
#15442 · #15449 · PR #17257 · #15807 · PR #17267 · #11509 (pm:on-hold, whether these doors should exist at all in v18) · #15829 (the dashboard widget.filter, the eighth door, not converged) · objectui#6206 (the ruling, closed) · objectui#6948 (the combinator finding, closed)
Generated by Claude Code
Filed by the
domain:specexecution seat,session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T06:40Z, out of the at-ACCEPT residue of PR #17257 (#15442 / #15449). ⛔ Unclaimed. Nodomain:*label and nopm:*state: ⛔ both are the triage seat's to produce. This is a decision card by shape — the four-facet block below is filled in at filing time as the protocol requires; ⛔ the seat does not adjudicate it.Measured on
origin/main501959b72.One sentence
A customer who saved a page last month with a filter written the old way will find it still renders exactly as before — right up until someone opens that page in the builder and hits Save, at which point the platform refuses the save and hands back a message that does not say how to fix it, and there is no tool that will fix it for them.
Background
Seven
filterdoors converged on the rule array[{ field, operator, value }, ...](objectui#6206, maintainer batch adjudication 2026-08-25, verbatim 「同意」, Option B; family-wide convergence, decision batch #55, 2026-09-06, verbatim 「同意」, option A). The shipped disposition is deliberate and is written into theelement-data-source-and-object-block-filter-rule-arrayentry in its own words:So today: read = fine, re-save = refused, no conversion, no rewriter.
The question the convergence left open
The three entries' own
replacementtext already spells out a total, mechanical mapping for the common shapes:{ status: 'active' }→[{ field: 'status', operator: 'equals', value: 'active' }]{ amount: { $gt: 100 } }→[{ field: 'amount', operator: 'greater_than', value: 100 }][['owner_id', '=', '{current_user_id}']]→[{ field: 'owner_id', operator: 'equals', value: '{current_user_id}' }]eq,ne,gt,notIn, …) accepted and normalized on parseThat mapping is losslessly executable by a machine for exactly that subset. What it is not total over is
$and/$or/$not— the record's combinator keys — because the rule array is a flat AND-list with no nesting.$and/$or/$notkeys "were misread by every gate block anyway (objectui#6948)" — that is the shipped entry's own words, and objectui#6948 is closed. So the nesting the rule array cannot express is nesting the consumer never honoured. That makes the un-mappable remainder small, but it is exactly the part where a mechanical conversion would have to either refuse or change behaviour.Governing text
os migrate meta --stored) vs §D3 (SemanticMigration— prose, converts nothing;packages/spec/src/migrations/types.ts).not-required (no-migration-prescription)clause refuses that disposition when the entry body carries a prescription. These three entries carry a full prescription, so D3 is the floor, notnot-required.packages/spec/src/conversions/stored.ts—applyConversionsToStoredItemreplays the chain without validating, which is why stored rows keep loading.Restart-touch:packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.tsRe-check before acting:
git log --oneline -20 origin/main -- packages/spec/src/migrations/entries/semantic/andgit grep -n "'element-data-source-and-object-block-filter-rule-array'" origin/main -- packages/spec/src/migrations/registry.ts— if a D2 has been added since, this card is already answered.Options and what each actually costs
invalid_typeas their only guidance (that message gap is a separate card). Nothing tells them how many such pages a tenant has.{k: {$op}}objects and single-level AST tuples; a combinator-carrying record is passed through untouchedos migrate meta --storedcleans up the great majority of stored rows in one operator-run pass; those pages re-save cleanly afterwards. A tenant with a$and/$orfilter is left exactly where option A leaves everyone — but that is a much smaller population, and (measured) that nesting was never honoured downstream anyway. Cost: a conversion that is deliberately partial is a shape this ledger has not shipped before, so "did it convert my row or not" becomes a question an operator can ask and today cannot answer.$andflattened into the AND-list and$or/$nothandled by some chosen rule$orhas no representation in a flat AND-list, so this option necessarily changes what some pages select — silently, during an operator-run migration, on data. That is the failure mode this platform's rules exist to prevent.Business translation
os-decision-facets
sys_metadata里的存量,不在仓里;仓内作者面已在同一个改动里全部改写(实测:绑定处 4 个 spec fixture、block 门 5 个 showcase 作者、3 个 lint fixture,仓外为零)。⇒ 若维护者判断线上存量接近零,零拉动的默认动作是 A。推荐:B(部分 D2),回退项 A。理由:它是唯一一个既降低客户侧意外、又不把静默行为改变引进迁移路径的选项;而它新增的义务有明确终点。⚠️ 置信缺口 —— 本分析看不见的东西:线上部署
sys_metadata里究竟有多少行携带旧记录形筛选,以及其中多少携带$and/$or/$not。这个数一旦接近零,推荐立刻翻成 A;它只能由维护者或一次面向部署的读数回答,⛔ 仓内测不出来。维护者速读
老页面里用旧写法存下来的筛选条件,现在读没问题、存会被拒,而且平台不提供任何自动修复。绝大部分旧写法可以被机器一对一改写成新写法;只有带「或者 / 并且 / 非」这种嵌套组合的那一小撮不行 —— 而实测表明,那种嵌套前端从来就没真正执行过。
请回一个字母:A / B / C。
Related
#15442 · #15449 · PR #17257 · #15807 · PR #17267 · #11509 (
pm:on-hold, whether these doors should exist at all in v18) · #15829 (the dashboardwidget.filter, the eighth door, not converged) · objectui#6206 (the ruling, closed) · objectui#6948 (the combinator finding, closed)Generated by Claude Code