Skip to content

Two open PRs narrow the same field-rule triad from one base: decide which owns it, and whether the surface gets one ADR-0087 id or two #19003

Description

@os-elon-musk

维护者速读

同一个"表单字段什么时候必填、什么时候只读、什么时候显示"的规则,现在有两位项目经理各带一个 PR 在改,两个都还没合,都从同一个基线出发,改到了 12 个相同的文件。两边跑出来的结果一模一样,写法不一样:一边给这三个槽位换了个共用的类型名,另一边把 36 个槽位逐个绑到已有的类型上。谁先合,另一边就得手工拆冲突,而且迁移台账会为同一块面记两条不同的账。

放着不管的代价不是"晚一点",是两个都动不了:谁先动都让另一边变成红的,而两边各自都已经通过了复核。

本席推荐 A。请回一个字母: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, both domain:spec, both carrying needs:contract-review, narrow the same FieldSchema field-rule triad:

PR #18985 (card #17778, this seat) PR #18638 (card #15811, os-litant)
ruling batch #119 item 3 (objectui#8069) batch #122 item 2
ruled on 2026-09-12 2026-09-12, six days before #17778 was filed against it
mechanism rebinds the aliases PredicateSchema / PredicateInputSchema to the evaluated schema; the triad composes them binds 36 slots directly to EvaluatedExpressionInputSchema; aliases left wide "deliberately"
scope 3 triad slots + fault-semantics record (ADR-0136), ADR-0089 addendum, ADR-0058 D7 roster entry, type-check DEBT producer fix 36 evaluated positions incl. gate slots, settings visibility, metrics / tracing
files 22 59
ADR-0087 entry field-rule-predicate-evaluated-slot-source-required evaluated-expression-slots-source-required

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.

Governing text

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...)
for n in 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)
for n in 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.

Execution after the ruling

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#3 at 2026-09-18T10:54Z. No domain:* and no priority:* set — those are the triage seat's to produce.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions