You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two open draft PRs, on the same base d8b12fca97c89449445200039bc3c2d6be7820bf, both domain:spec, both carrying needs:contract-review, narrow the same FieldSchema field-rule triad:
12 paths overlap, measured: packages/spec/src/shared/expression.zod.ts, packages/spec/src/data/field.zod.ts, packages/spec/src/migrations/registry.ts, six packages/spec/api-surface-declarations/*.txt, and three content/docs/references/**.mdx. Textual conflict is certain on the triad lines, the cel / expression declarations and the step-18 migration insert. The runtime outcome for the triad is identical under either.
ADR-0087 D3 governs the migration ledger: one surface, one notified id. Two ids for one surface is the defect this decision avoids.
Searched evaluated,predicate,expression,narrowing,alias over docs/adr, AGENTS.md, packages/spec/src at origin/main f347c793e: 29 ADR decision candidates, top tier ADR-0058 D6/D7/D8. No prior ruling picks between an alias-level and a slot-level binding. That is why this is a card and not a seat call.
Protocol declaration
No protocol change either way — both PRs already declare the same accept-set narrowing (minor + BREAKING, ADR-0087 D3 entry). What changes is which symbols are publicly narrowed: under A, Predicate / PredicateInput stay wide and are never published in a narrowed form; under B and C they are published narrowed and become a permanent obligation.
Premises, with re-check commands
# both PRs still on one base (expect the same base sha, d8b12fca97...)fornin 18638 18985;do curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/objectstack-ai/objectstack/pulls/$n" \
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{const p=JSON.parse(d);console.log(p.number,p.base.sha,p.head.sha,p.draft);});';done# the 12-path overlap (expect 12)fornin 18638 18985;do curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/objectstack-ai/objectstack/pulls/$n/files?per_page=100" \
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{JSON.parse(d).forEach(f=>console.log(f.filename));});' \
| sort > /tmp/f-$n.txt;done
comm -12 /tmp/f-18638.txt /tmp/f-18985.txt | wc -l
# the load-bearing axis-1 reading: the aliases had ZERO slot composers before #18985
git fetch origin main && git grep -nE "^\s*[a-zA-Z]+\s*:\s*Predicate(Input)?Schema" \
origin/main -- 'packages/spec/src/**'':!*.test.ts'| wc -l # expect 0
git grep -nE "^\s*[a-zA-Z]+\s*:\s*ExpressionInputSchema" \
origin/main -- 'packages/spec/src/**'':!*.test.ts'| wc -l # lit control, expect 29
The question
Which PR owns the triad narrowing, and does the FieldSchema triad surface get one ADR-0087 notified id or two?
Options and real cost
option
what happens
cost the customer can feel
A
#18985 drops the alias rebinding and the three triad lines, keeping ADR-0136, the ADR-0089 addendum, the D7 roster entry and the DEBT producer fix; #18638 is the only narrowing PR; one notified id
#18985's clause-② PASS covered the alias narrowing, so its code half needs a fresh review, not a re-affirmation — real rework in this lane. ADR-0136 D1's sentence must be rewritten to describe #18638's binding
B
#18985 lands first as written; #18638 rebases, drops the three duplicated lines, keeps its other 33 positions; two ids, cross-referenced
two public type exports (Predicate, PredicateInput) published in narrowed form with zero composers — a permanent obligation bought for nothing; the ledger carries two ids for one surface, which every future reader of that surface must reconcile
C
one PR under one card
discards one of the two seats' completed reviews and collapses two rulings into one seat's PR; the largest single diff and the slowest path to any landing
Business translation
A = "there is exactly one way to say this expression will really be run, and everything points at it." One vocabulary word, 36 places using it.
B = "there are two synonyms for the same rule, both published, both permanent." Ships sooner in this lane, and every later author has to learn which of two identical names to reach for.
C = "stop both, rebuild as one." Cleanest ledger, longest blockage, and it throws away review work already done and paid for.
Anti-AI-error axis — what a wrong choice looks like
Under A a wrong metadata predicate is refused loudly at the slot, by name, at authoring time. Under B the failure is not a refusal at all but a choice: an AI author picking PredicateInputSchema and an AI author picking EvaluatedExpressionInputSchema both produce valid, identical-strength metadata, so nothing anywhere ever signals that one of the two names was the wrong reach — the cost is silent, permanent vocabulary drift rather than a visible error. That asymmetry, not the landing order, is the reason this axis favours A.
Recommendation
A. Fallback: B, if the maintainer judges this lane's rework cost higher than carrying two synonymous public types — B is coherent and ships this lane sooner, it just buys a permanent obligation with zero measured demand.
Self-check: 只看①选 A;②③④ 是否翻转:否 —— all four point the same way, so the letter is not being propped up by one axis.
Confidence gap, stated plainly. I have NOT read #18638's 59-file diff and I have not verified its quality; its clause-② verdict is a thread verdict with 0 GitHub reviews on the PR, which I did not audit. So I am recommending that the work concentrate in a PR I have not reviewed. If #18638 turns out to be weaker than #18985, A costs more than this card claims and B becomes the better letter. Also note the bias this recommendation runs against: A shrinks my own card's scope and sends the substantive narrowing to another seat's PR — I state that so it is visible rather than argued around.
维护者速读
同一个"表单字段什么时候必填、什么时候只读、什么时候显示"的规则,现在有两位项目经理各带一个 PR 在改,两个都还没合,都从同一个基线出发,改到了 12 个相同的文件。两边跑出来的结果一模一样,写法不一样:一边给这三个槽位换了个共用的类型名,另一边把 36 个槽位逐个绑到已有的类型上。谁先合,另一边就得手工拆冲突,而且迁移台账会为同一块面记两条不同的账。
放着不管的代价不是"晚一点",是两个都动不了:谁先动都让另一边变成红的,而两边各自都已经通过了复核。
source#18638)独家负责所有槽位收紧。本席推荐 A。请回一个字母:A / B / C。
Predicate*这两个别名的组合者数量为 0 —— 它是个死符号,spec: hold a predicate to what the engine can run; declare its fault semantics (ADR-0136) #18985 把它复活成载体,于是平台里就有了两个名字(PredicateInputSchema与EvaluatedExpressionInputSchema)表达同一件事"这个表达式真的会被执行"。两年后一个健康的平台在这件事上应当只有一个可组合的类型名,主流建模同向(Salesforce 公式字段、Airtable、Retool 都收敛到单一"已校验表达式"类型)。A 让收紧只落在既有的EvaluatedExpression*上,别名不复活。ast的谓词,门禁静默变成"没有门禁"。feat(spec)!: every engine-evaluated expression slot requires a non-blanksource#18638 覆盖 36 个位置,spec: hold a predicate to what the engine can run; declare its fault semantics (ADR-0136) #18985 覆盖 3 个;同样一次落地,A 让更多作者今天就被响亮拒绝。Predicate与PredicateInput是公开导出的类型,一旦以收紧后的形态发布,就是永久义务 —— 每个已声明的键都是永久义务,而这一个的组合者是零。A 不新增这份义务;B 与 C 都把它固化。Prior rulings read: evaluated,predicate,expression,narrowing,alias → 29 hits; ADR-0058 D7, ADR-0087 D3, ADR-0089
Background
Two open draft PRs, on the same base
d8b12fca97c89449445200039bc3c2d6be7820bf, bothdomain:spec, both carryingneeds:contract-review, narrow the sameFieldSchemafield-rule triad:PredicateSchema/PredicateInputSchemato the evaluated schema; the triad composes themEvaluatedExpressionInputSchema; aliases left wide "deliberately"field-rule-predicate-evaluated-slot-source-requiredevaluated-expression-slots-source-required12 paths overlap, measured:
packages/spec/src/shared/expression.zod.ts,packages/spec/src/data/field.zod.ts,packages/spec/src/migrations/registry.ts, sixpackages/spec/api-surface-declarations/*.txt, and threecontent/docs/references/**.mdx. Textual conflict is certain on the triad lines, thecel/expressiondeclarations and the step-18 migration insert. The runtime outcome for the triad is identical under either.Governing text
ExpressionInputSchemaslot an engine evaluates (formulaexpression, validation / hook / sharingcondition,visibleWhen…) still accepts anast-only or blank-sourceenvelope #15811, comment5644350409(os-tesla, batch 🔗 Broken links detected in documentation #122 item 2), item 1 verbatim: 「EvaluatedExpressionSchema… composes into every evaluated slot the census found …: formulaexpression; field / option / grid-columnvisibleWhen/readonlyWhen/requiredWhen; … ⛔ By identity on all 36 positions」; item 2: 「ExpressionSchema/ExpressionInputSchema(persistence) keepsourceORast.」 It does not namePredicateInputSchema, so no ruling constrains that symbol either way.ADR-0087D3 governs the migration ledger: one surface, one notified id. Two ids for one surface is the defect this decision avoids.evaluated,predicate,expression,narrowing,aliasoverdocs/adr,AGENTS.md,packages/spec/srcatorigin/main f347c793e: 29 ADR decision candidates, top tier ADR-0058 D6/D7/D8. No prior ruling picks between an alias-level and a slot-level binding. That is why this is a card and not a seat call.Protocol declaration
No protocol change either way — both PRs already declare the same accept-set narrowing (
minor+ BREAKING, ADR-0087 D3 entry). What changes is which symbols are publicly narrowed: under A,Predicate/PredicateInputstay wide and are never published in a narrowed form; under B and C they are published narrowed and become a permanent obligation.Premises, with re-check commands
The question
Which PR owns the triad narrowing, and does the
FieldSchematriad surface get one ADR-0087 notified id or two?Options and real cost
Predicate,PredicateInput) published in narrowed form with zero composers — a permanent obligation bought for nothing; the ledger carries two ids for one surface, which every future reader of that surface must reconcileBusiness translation
Anti-AI-error axis — what a wrong choice looks like
Under A a wrong metadata predicate is refused loudly at the slot, by name, at authoring time. Under B the failure is not a refusal at all but a choice: an AI author picking
PredicateInputSchemaand an AI author pickingEvaluatedExpressionInputSchemaboth produce valid, identical-strength metadata, so nothing anywhere ever signals that one of the two names was the wrong reach — the cost is silent, permanent vocabulary drift rather than a visible error. That asymmetry, not the landing order, is the reason this axis favours A.Recommendation
A. Fallback: B, if the maintainer judges this lane's rework cost higher than carrying two synonymous public types — B is coherent and ships this lane sooner, it just buys a permanent obligation with zero measured demand.
Self-check: 只看①选 A;②③④ 是否翻转:否 —— all four point the same way, so the letter is not being propped up by one axis.
Confidence gap, stated plainly. I have NOT read #18638's 59-file diff and I have not verified its quality; its clause-② verdict is a thread verdict with 0 GitHub reviews on the PR, which I did not audit. So I am recommending that the work concentrate in a PR I have not reviewed. If #18638 turns out to be weaker than #18985, A costs more than this card claims and B becomes the better letter. Also note the bias this recommendation runs against: A shrinks my own card's scope and sends the substantive narrowing to another seat's PR — I state that so it is visible rather than argued around.
Execution after the ruling
source#18638's binding, renumbers ADR-0136 to 0137 (PR docs(adr): ADR-0136 declared journeys as the priority anchor #18480 claimed 0136 about 42 hours earlier;0137measured free on main and across all 32 open PRs), cites batch 🔗 Broken links detected in documentation #122 / spec: the evaluated-slot rule of #15430 reaches only the flow-node ledger — every otherExpressionInputSchemaslot an engine evaluates (formulaexpression, validation / hook / sharingcondition,visibleWhen…) still accepts anast-only or blank-sourceenvelope #15811 / feat(spec)!: every engine-evaluated expression slot requires a non-blanksource#18638 in the Scope boundary, folds the ADR-0087 entry into a cross-reference, and fixes the migration entry'ssurfacewording. Then a fresh at-tier clause-② review, then the record, then it parks for human approval as a governed record.ExpressionInputSchemaslot an engine evaluates (formulaexpression, validation / hook / sharingcondition,visibleWhen…) still accepts anast-only or blank-sourceenvelope #15811 or feat(spec)!: every engine-evaluated expression slot requires a non-blanksource#18638 beyond evidence already posted (comment5728924245, no label, assignee or state change), and does not approve or enqueue spec: hold a predicate to what the engine can run; declare its fault semantics (ADR-0136) #18985 —docs/adr/**is governed.Related cards and PRs
#17778 (this lane,
pm:dispatched), #15811 (os-litant,pm:dispatched), PR #18985, PR #18638, PR #18480 (ADR number collision), #19000 (closed as duplicate of #15811 — my error, recorded there).Filed by
Seat: domain:spec#3at 2026-09-18T10:54Z. Nodomain:*and nopriority:*set — those are the triage seat's to produce.Generated by Claude Code