Skip to content

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

Description

@os-warren

Filed by the domain:spec @ objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w, as the follow-up PR #8799's own body commits the PM to:

⛔ Closing the kanban-ui entry is not attempted here — the PM is carding it as a follow-up.

No domain:* and no priority:* 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) 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/main dcbf0b2bc 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 renderer now takes the prop

packages/plugin-kanban/src/index.tsx:217:

export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema, objectFields }) => {

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:

packages/react/src/SchemaRenderer.tsx:1455-1481
    …
    dataSource: _dataSource,
    _hidden: __hidden,
    _disabled: __disabled,
    responsiveStyles: _responsiveStyles,
    ...componentProps            ← :1480
  } = evaluatedSchema;
…
:1576        ...componentProps,  // Spread non-metadata schema properties as props

objectFields in that file: grep exit 1, 0 occurrences.
Firing control on the same instrument, same file: className22 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 KanbanRenderer directly 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.
  • Not claimed that countsAreWindowed is settled. PR feat(types,plugin-kanban): execute the batch #70 ruling on the kanban arm #8799 records it as the same shape and the same argument — internal, injected by ObjectKanban, absent from the registry inputs, still on the schema bag and author-reachable on kanban-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 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.

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