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
finding(react,plugin-kanban): an AUTHORED objectFields still reaches the predicate layer on the kanban-ui entry — batch #70 closed the kanban arm, not the key #8818
objectui#7742 / PR #8799 (merged dcbf0b2bc, 2026-09-09) moved objectFields off the schema bag onto a real React prop on KanbanRendererProps. The stated reason was that inside schema the key was reachable by an author: BaseSchema is .passthrough(), no schema face declares or judges the key, and the predicate layer uses it to decide how a conditional-formatting rule reads a field — so an authored catalogue can change which cards a rule matches.
That move closes the kanban arm. It does not close the key, and PR #8799 says so in three places. This card is the part it deliberately left open.
Measured on origin/maindcbf0b2bc by this seat — ⛔ not transcribed from the PR
The registration face
packages/plugin-kanban/src/index.tsx, every ComponentRegistry.register( call in the file:
line
type key
renderer
:252
kanban-ui
KanbanRenderer
:351
kanban-enhanced
inline ({ schema })
:579
object-kanban
ObjectKanbanRenderer
:589
kanban
ObjectKanbanRenderer
⚠️ Note for anyone carrying forward the objectui#8410 screen: that screen has this mapping inverted (it reads KanbanRenderer as serving kanban). PR #8799 corrected it and the table above is a fresh reading on the merged tree.
The strip list does not name the key — and this is the whole mechanism
packages/react/src/SchemaRenderer.tsx builds its React props by destructuring a fixed, explicitly enumerated list of metadata keys off the node and spreading the rest:
objectFields in that file: grep exit 1, 0 occurrences. Firing control on the same instrument, same file:className → 22 occurrences, exit 0.
⇒ The zero is a reading. objectFields is not stripped, so on a kanban-ui node an authored objectFields survives into ...componentProps and is spread as a React prop — landing on the very prop PR #8799 added, and reaching resolveConditionalFormatting exactly as it did before the move.
The behavioural evidence
⚠️ Attributed, not re-run here: PR #8799 measured this end-to-end through the real SchemaRenderer, with the positive leg asserted first as the firing control for the negative:
node
authored objectFields
outcome
kanban-ui
absent
unpainted
kanban-ui
present
painted rgb(255, 0, 0)
kanban
present
unpainted
The kanban row is the ruled arm genuinely closing: ObjectKanbanRenderer serves that key and discards its rest-spread (void _props;), so the value reaches nothing.
⚠️ Its pin — packages/plugin-kanban/src/__tests__/objectFieldsIsAPropNotASchemaKey-7742.test.tsx — renders KanbanRendererdirectly and therefore never exercises SchemaRenderer. So the two kanban-ui rows above are measured but not pinned. That is the gap this card sits in: nothing in the repository will go red if the behaviour changes in either direction.
What is NOT claimed here
⛔ Not claimed that this is a vulnerability with a named exploit.kanban-ui is the schema-only entry: its data arrives whole from the same author who would write the catalogue. Whether an author fooling their own board is a defect or a non-event depends on who authors these documents in a deployment, and this seat does not know that.
⛔ Not claimed that stripping the key is free.objectFields is now a declared prop on a published component's props interface; a consumer rendering KanbanRenderer directly passes it legitimately. Any strip must land at the SchemaRenderer boundary or on a kanban-ui schema arm, ⛔ never by removing the prop.
The decision this card asks for — ⛔ none chosen here
Enforce-or-remove (ADR-0049) on an author-reachable channel that no face judges:
(a) Strip objectFields at the SchemaRenderer boundary — add it to the enumerated metadata list at :1455-1481. Closes every entry at once, including any future renderer. ⚠️ Cost: that list is a global strip; a component that legitimately wants an authored objectFields React prop from a node loses it silently. Nothing in the repo does today — that is a claim the implementer must measure, not inherit from this card.
(b) Refuse objectFields by name on a kanban-ui schema arm — a retirementTombstone, the mechanism PR #8799 used four times. Loud, local, and it tells the author what happened. ⚠️ Cost: kanban-ui would need an arm that declares it; check whether one exists before pricing this.
(c) Declare and judge it — make the catalogue an authorable, validated input. ⚠️ This is the direction batch #70 rejected for the kanban arm; taking it here would leave the two entries of one plugin disagreeing about the same key, which is the shape objectui#8801 and objectui#8802 are already open about.
(d) Accept it, and pin it as accepted — record that kanban-ui is an author-owned surface where the catalogue is the author's to supply, and add the SchemaRenderer-exercising pin the current one deliberately isn't, so the behaviour stops being merely measured.
This seat's recommendation: (a), with (d)'s pin regardless of which route wins. Reason: the key's only legitimate producer is ObjectKanban, which supplies it as a React prop and never through a node, so a strip at the boundary costs nothing measured and closes the class rather than one entry. But ⛔ (a) is the option that needs the "nothing legitimately authors it" measurement, and this seat has not taken it — that measurement is the first thing whoever implements owes.
⚠️Whichever way this is ruled, the pin is not optional. Today both kanban-ui rows are measured and unpinned, so the next refactor can move this behaviour in silence in either direction — which is the same failure mode that let the original defect ship.
Refs: objectui#7742 (the ruled card) · PR #8799 (which measured this and scoped it out) · objectui#8801 (ObjectKanbanSchema.allowCollapse, one arm over) · objectui#8802 (the registry inputs divergence) · objectui#8307 (countsAreWindowed) · objectui#8410 (the screen whose registration mapping is inverted) · ADR-0049.
Filed by the
domain:spec@ objectui PM seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, as the follow-up PR #8799's own body commits the PM to:⛔ No
domain:*and nopriority:*applied — routing and grading belong to triage, and this seat does not produce them.What batch #70 actually closed
objectui#7742 / PR #8799 (merged
dcbf0b2bc, 2026-09-09) movedobjectFieldsoff theschemabag onto a real React prop onKanbanRendererProps. The stated reason was that insideschemathe key was reachable by an author:BaseSchemais.passthrough(), no schema face declares or judges the key, and the predicate layer uses it to decide how a conditional-formatting rule reads a field — so an authored catalogue can change which cards a rule matches.That move closes the
kanbanarm. It does not close the key, and PR #8799 says so in three places. This card is the part it deliberately left open.Measured on
origin/maindcbf0b2bcby this seat — ⛔ not transcribed from the PRThe registration face
packages/plugin-kanban/src/index.tsx, everyComponentRegistry.register(call in the file::252kanban-uiKanbanRenderer:351kanban-enhanced({ schema }):579object-kanbanObjectKanbanRenderer:589kanbanObjectKanbanRendererKanbanRendereras servingkanban). PR #8799 corrected it and the table above is a fresh reading on the merged tree.The renderer now takes the prop
packages/plugin-kanban/src/index.tsx:217:The strip list does not name the key — and this is the whole mechanism
packages/react/src/SchemaRenderer.tsxbuilds its React props by destructuring a fixed, explicitly enumerated list of metadata keys off the node and spreading the rest:objectFieldsin that file: grep exit 1, 0 occurrences.Firing control on the same instrument, same file:
className→ 22 occurrences, exit 0.⇒ The zero is a reading.
objectFieldsis not stripped, so on akanban-uinode an authoredobjectFieldssurvives into...componentPropsand is spread as a React prop — landing on the very prop PR #8799 added, and reachingresolveConditionalFormattingexactly as it did before the move.The behavioural evidence
SchemaRenderer, with the positive leg asserted first as the firing control for the negative:objectFieldskanban-uikanban-uirgb(255, 0, 0)kanbanThe
kanbanrow is the ruled arm genuinely closing:ObjectKanbanRendererserves that key and discards its rest-spread (void _props;), so the value reaches nothing.packages/plugin-kanban/src/__tests__/objectFieldsIsAPropNotASchemaKey-7742.test.tsx— rendersKanbanRendererdirectly and therefore never exercisesSchemaRenderer. So the twokanban-uirows above are measured but not pinned. That is the gap this card sits in: nothing in the repository will go red if the behaviour changes in either direction.What is NOT claimed here
kanban-uiis the schema-only entry: itsdataarrives whole from the same author who would write the catalogue. Whether an author fooling their own board is a defect or a non-event depends on who authors these documents in a deployment, and this seat does not know that.objectFieldsis now a declared prop on a published component's props interface; a consumer renderingKanbanRendererdirectly passes it legitimately. Any strip must land at theSchemaRendererboundary or on akanban-uischema arm, ⛔ never by removing the prop.countsAreWindowedis settled. PR feat(types,plugin-kanban): execute the batch #70 ruling on thekanbanarm #8799 records it as the same shape and the same argument — internal, injected byObjectKanban, absent from the registryinputs, still on the schema bag and author-reachable onkanban-ui. Batch Add cloud storage functionality with Vercel KV integration in studio #70 did not name it. objectui#8307 owns the key; whichever way this card is ruled should say whether that ruling reaches it.The decision this card asks for — ⛔ none chosen here
Enforce-or-remove (ADR-0049) on an author-reachable channel that no face judges:
(a) Strip⚠️ Cost: that list is a global strip; a component that legitimately wants an authored
objectFieldsat theSchemaRendererboundary — add it to the enumerated metadata list at:1455-1481. Closes every entry at once, including any future renderer.objectFieldsReact prop from a node loses it silently. Nothing in the repo does today — that is a claim the implementer must measure, not inherit from this card.(b) Refuse⚠️ Cost:
objectFieldsby name on akanban-uischema arm — aretirementTombstone, the mechanism PR #8799 used four times. Loud, local, and it tells the author what happened.kanban-uiwould need an arm that declares it; check whether one exists before pricing this.(c) Declare and judge it — make the catalogue an authorable, validated input.⚠️ This is the direction batch #70 rejected for the
kanbanarm; taking it here would leave the two entries of one plugin disagreeing about the same key, which is the shape objectui#8801 and objectui#8802 are already open about.(d) Accept it, and pin it as accepted — record that
kanban-uiis an author-owned surface where the catalogue is the author's to supply, and add theSchemaRenderer-exercising pin the current one deliberately isn't, so the behaviour stops being merely measured.This seat's recommendation: (a), with (d)'s pin regardless of which route wins. Reason: the key's only legitimate producer is
ObjectKanban, which supplies it as a React prop and never through a node, so a strip at the boundary costs nothing measured and closes the class rather than one entry. But ⛔ (a) is the option that needs the "nothing legitimately authors it" measurement, and this seat has not taken it — that measurement is the first thing whoever implements owes.kanban-uirows are measured and unpinned, so the next refactor can move this behaviour in silence in either direction — which is the same failure mode that let the original defect ship.Refs: objectui#7742 (the ruled card) · PR #8799 (which measured this and scoped it out) · objectui#8801 (
ObjectKanbanSchema.allowCollapse, one arm over) · objectui#8802 (the registryinputsdivergence) · objectui#8307 (countsAreWindowed) · objectui#8410 (the screen whose registration mapping is inverted) · ADR-0049.