Observation-class finding, measured while accepting PR #8297 (objectui#6950). Filed unassigned, no labels — grading is the triage seat's.
The claim
The 2026-09-07 ruling on #6950 (director seat, decision batch #69, maintainer 「其他同意」) retires the zero-writer 'field' arm under ADR-0049, naming one coordinate: RegistryConfigLike.binding in packages/sdui-parser/src/index.ts. PR #8297 narrows exactly that one, which is correct scope discipline and was accepted as such.
The same arm survives on the manifest reader face, in two more declarations that the ruling did not name, so after #8297 lands the published package narrows on one face and stays wide on the other.
Measured on origin/main = 8f9d87a
packages/sdui-parser/src/index.ts:90 binding?: 'object' | 'field'; ← RegistryConfigLike — narrowed by PR #8297
packages/sdui-parser/src/types.ts:100 binding?: 'object' | 'field'; ← ManifestInput — RESIDUE
packages/sdui-parser/src/types.ts:138 bindings: Array<{ tag: string; input: string; kind: 'object' | 'field'; value: unknown }>; ← ManifestValidationResult — RESIDUE
Zero writers, with a live control so the zero means something. binding: 'field' across packages/ / apps/ / examples/: 0 occurrences. The control word binding: 'object' on the same grep: 7 occurrences across 5 files — packages/core/src/data-scope/element-data-source.ts (3), and one each in sdui-parser's __tests__/compile.test.ts, __tests__/render.test.tsx, __tests__/tier.test.ts, verify.ts. So the discriminator works and the zero is a real zero, not a broken pattern.
⭐ The shadow: objectstack carries the same three declarations
Measured on objectstack-ai/objectstack origin/main = f2f6684:
packages/sdui-parser/src/index.ts:81 binding?: 'object' | 'field';
packages/sdui-parser/src/types.ts:84 binding?: 'object' | 'field';
packages/sdui-parser/src/types.ts:107 bindings: Array<{ tag: string; input: string; kind: 'object' | 'field'; value: unknown }>;
⛔ I have NOT measured which repo is upstream of which, and this card does not assert a direction. What is measured is that both repos carry a packages/sdui-parser with the same three declarations, and that this repo runs a check:upstream-port-parity gate — so the two copies are kept in step by some mechanism, and whoever owns that mechanism decides whether a retirement on one side must propagate. The objectstack half is filed as objectstack-ai/objectstack#16583; ⛔ neither card should be worked without the other being read.
⚠️ Also unmeasured, and named on the objectstack card: the 'field' writer count in that repo. The zero above is objectui's. Whoever takes either side runs the grep on their own side first.
Why this is worth a card rather than a note
The reviewing tier explicitly escalated it from "noted" to must be filed. The reason is the shape, not the size: a published type that narrows on the producer face and stays wide on the reader face is a declared-but-unenforced arm with a new asymmetry added to it — which is the exact defect class #6950 was opened about, one face over. validateTree forwards whatever a manifest says, so nothing downstream refuses a 'field' that no producer can now legally emit.
⚠️ There is a real argument for the current state being correct rather than incoherent, and it should be answered rather than assumed away: producer → reader is a subset relation, so a reader accepting a value no producer emits is merely permissive, not wrong; and the wire vocabulary is shared with a separately published @objectstack/sdui-parser. Whoever takes this decides between "narrow both faces" and "record the reader face as deliberately wider, and say why" — ⛔ what it should not stay is undecided and unstated.
Refs
Filed by the domain:spec @ objectui PM seat, session session_01QtGhnU3WnnWyiWeYQhw2aX.
Generated with Claude Code
https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
Observation-class finding, measured while accepting PR #8297 (objectui#6950). Filed unassigned, no labels — grading is the triage seat's.
The claim
The 2026-09-07 ruling on #6950 (director seat, decision batch #69, maintainer 「其他同意」) retires the zero-writer
'field'arm under ADR-0049, naming one coordinate:RegistryConfigLike.bindinginpackages/sdui-parser/src/index.ts. PR #8297 narrows exactly that one, which is correct scope discipline and was accepted as such.The same arm survives on the manifest reader face, in two more declarations that the ruling did not name, so after #8297 lands the published package narrows on one face and stays wide on the other.
Measured on
origin/main=8f9d87aZero writers, with a live control so the zero means something.
binding: 'field'acrosspackages//apps//examples/: 0 occurrences. The control wordbinding: 'object'on the same grep: 7 occurrences across 5 files —packages/core/src/data-scope/element-data-source.ts(3), and one each insdui-parser's__tests__/compile.test.ts,__tests__/render.test.tsx,__tests__/tier.test.ts,verify.ts. So the discriminator works and the zero is a real zero, not a broken pattern.⭐ The shadow: objectstack carries the same three declarations
Measured on
objectstack-ai/objectstackorigin/main=f2f6684:⛔ I have NOT measured which repo is upstream of which, and this card does not assert a direction. What is measured is that both repos carry a
packages/sdui-parserwith the same three declarations, and that this repo runs acheck:upstream-port-paritygate — so the two copies are kept in step by some mechanism, and whoever owns that mechanism decides whether a retirement on one side must propagate. The objectstack half is filed as objectstack-ai/objectstack#16583; ⛔ neither card should be worked without the other being read.'field'writer count in that repo. The zero above is objectui's. Whoever takes either side runs the grep on their own side first.Why this is worth a card rather than a note
The reviewing tier explicitly escalated it from "noted" to must be filed. The reason is the shape, not the size: a published type that narrows on the producer face and stays wide on the reader face is a declared-but-unenforced arm with a new asymmetry added to it — which is the exact defect class #6950 was opened about, one face over.
validateTreeforwards whatever a manifest says, so nothing downstream refuses a'field'that no producer can now legally emit.@objectstack/sdui-parser. Whoever takes this decides between "narrow both faces" and "record the reader face as deliberately wider, and say why" — ⛔ what it should not stay is undecided and unstated.Refs
bindinginput instead of casting it in #8297 — the producer-face retirement and its Clause-② review (the review comment escalating this:ComponentInputdoes not declarebinding, but the manifest serializer publishes it andvalidateTreereads it — authoring it needs a cast #6950 (comment)).binding: 'field'arm with zero writers on both sides of the port — objectui is retiring its producer-face half, and nothing propagates that here objectstack#16583 — the objectstack half.assertFullyLoaded/RegistryConfigLike.lazy), checked and ⛔ not a duplicate of this.Filed by the
domain:spec @ objectuiPM seat, sessionsession_01QtGhnU3WnnWyiWeYQhw2aX.Generated with Claude Code
https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX