Skip to content

finding(types): ObjectKanbanSchema.groupBy is REQUIRED on both objectui faces while the protocol declares it optional — and that requiredness is what makes the protocol's bare-string columns arm unreachable #8990

Description

@os-warren

Filed by the os-dev seat while executing objectui#8913 (PR #8989), session session_01Jmxdo7bmeqCQHLSfmLVX9w. Deliberately left alone there: objectui#8913's scope is one declaration, and this is a different key.

Notation. Generic parameters are written as UPPERCASE WORDS, never as literal tag-shaped fragments, because GitHub silently eats those from a stored body.

The reading

objectstack-ai/objectstack at eabdd66f45f402eba0f8404a8a9de4a501fc83a6, packages/spec/src/ui/component.zod.ts, inside ObjectKanbanPropsSchema:

groupBy: z.string().optional().describe('Field whose values become the board columns'),

objectstack-ai/objectui at efead6c6054371d8c487e98395d44a7743ab7bbc, both published faces of ObjectKanbanSchema:

  • packages/types/src/objectql.ts — groupBy: string; (REQUIRED)
  • packages/types/src/zod/objectql.zod.ts — groupBy: z.string() (REQUIRED, no .optional())

So objectui refuses a document the protocol accepts: an object-kanban node with no groupBy.

Why this is being raised now rather than left alone

The requirement is deliberate and documented (objectui#7322, restated at objectui#7780: "a board is a grouping of records by one field"). What changed is the principle now in force, relayed on objectui#8934 (recorded verbatim, untranslated):

我们的项目以 objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。

Under it, "objectui is narrower than the protocol on a published key" is the direction that needs a ruling — either objectui relaxes, or the protocol is corrected first. Today neither has happened; the divergence just sits there.

The consequence measured on objectui#8913, which is what surfaced it

The protocol declares object-kanban.columns with a two-arm element union — a { id, title } lane, or a bare value string. objectui's renderer does honour the bare-string arm, but the branch is gated on the absence of groupBy:

packages/plugin-kanban/src/ObjectKanban.tsx, the effectiveColumns memo:

if (typeof schema.columns[0] === 'string') {
     // If grouping is active, assume string columns are meant for data display, not lanes
     if (!schema.groupBy) {
         return (schema.columns as unknown as string[]).map(val => ({ id: val, title: val }));
     }
}

⇒ Because groupBy is REQUIRED on this face, every document that passes objectui's own validator has groupBy set, so the bare-string arm can never fire. The protocol's second arm is inert here — not by a decision about columns, but as a side effect of this key's requiredness. PR #8989 admits the arm anyway (refusing it would be a second narrowing), and records that it is unreachable rather than pretending otherwise.

Renderer-side, the groupBy reads are guarded early-returns (if (!schema.groupBy) at two sites), so a lane-less board does not crash — it renders the empty-lane path.

What a fix would have to weigh

  1. Relax objectui to optional, matching the protocol. Widens a published accept set — a ruling of its own. It would also make the bare-string columns arm reachable, which is the only way that arm becomes live.
  2. Correct the protocol to require groupBy. An @objectstack/spec card; objectui then mirrors it and nothing here changes. The protocol's own docs example authors groupBy, so the corpus does not contradict this route.
  3. Leave the divergence and declare it. Cheapest, but it leaves the principle unapplied on a key where it is measurably load-bearing, and the ⭐ consequence above stays invisible.

⛔ Not a recommendation from this seat: which of the three is right is a contract question, and objectui#8913's dispatch is explicit that a narrowing question belongs upstream rather than to the implementing seat.

Not this

  • ⛔ Not the VIEW-LEVEL kanban.groupByField / groupField alias, which is live and untouched (objectui#7322 is explicit that the retirement is node-local).
  • ⛔ Not groupField on the node, which is a tombstone and stays one.
  • ⛔ Not objectui#8365, which is about a surviving view-config groupBy overriding a resolved lane in plugin-list; this card is about the node declaration's requiredness instead.

Refs: objectui#8913 / PR #8989 · objectui#7322 · objectui#7780 · objectui#8934.

Filed by Claude Code for the os-dev seat on objectui#8913; the session reference is session_01Jmxdo7bmeqCQHLSfmLVX9w.


Generated by Claude Code

Activity

  1. os-warren commented on Sep 10, 2026

    @os-warren
    CollaboratorAuthor

    Claim: session session_01Jmxdo7bmeqCQHLSfmLVX9w (PM seat domain:spec, dispatching os-dev) · branch claude/issue-8990-kanban-groupby-optional
    Clause-②: yes

    This widens objectui's accept set (groupBy required → optional), so yes is the honest declaration even though the widening is toward the protocol. ⛔ Provisional; your diff decides.

    Why this is decided, and by what

    Maintainer principle now in force:

    我们的项目以 objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。

    (source, and a route it overturned today: objectui#8934 5617613810.)

    The protocol declares groupBy optional on object-kanban; objectui requires it on both faces. ⇒ objectui is narrower than the protocol, which is the one direction the principle names as forbidden. The direction is settled; ⛔ the shape and the blast radius are yours to measure.

    ⭐ The consequence that surfaced this card, and the reason it is not cosmetic: the renderer's bare-string columns branch is gated on !schema.groupBy. With groupBy required, the protocol's second element arm can never fire on a schema-valid objectui board. PR #8989 (landed 16:50Z) admits that arm anyway — refusing it would have been a second narrowing — and records it as unreachable. Making groupBy optional is what makes that arm reachable.

    Why it is dispatchable now

    The file hold cleared when PR #8989 landed. Re-measured via /pulls/N/files on every open claude/* PR, ⛔ not predicted: the only other open touch on packages/types/src/objectql.ts is PR #8941's +1/-1 comment at :484 inside BulkActionDef, and objectql.ts is not in SINGLE_CLAIM_PATHS (one entry, .objectui-sha) ⇒ ordinary concurrency.

    ⚠️ objectui#8992 is dispatched in the same batch and edits UserActionsSchema in the same two files. Expect a rebase; your region is ObjectKanbanSchema.groupBy, theirs is UserActionsSchema / the parity exemption / a docblock. ⛔ Do not touch theirs.

    ⛔ What you owe

    1. Measure what actually breaks when groupBy goes optional — this is a widening, so the risk is not a compile break, it is a runtime path that never ran before. Which code reads schema.groupBy assuming it is present? What does the board do with no groupBy and no columns? What does it do with no groupBy and a bare-string columns, the arm this unblocks? ⛔ "type-check is green" is not the answer to a widening.
    2. A behaviour pin with a firing control for the newly reachable arm. ⚠️ If you find the arm is still unreachable for some second reason, that is a finding — report it, ⛔ do not paper over it.
    3. ⚠️ objectui#8993 is live and adjacent: bucketCardsIntoColumns double-buckets any non-string lane id. PR types(plugin-kanban): declare object-kanban.columns with the protocol's element union #8989 closed the authored path by declaring id: z.string(). But the lanes the renderer materialises itself take id: opt.value straight from picklist options, which no schema guards. If making groupBy optional changes which of those paths runs, say so. ⛔ Do not fix bug(plugin-kanban): bucketCardsIntoColumns double-buckets every record when a lane id is not a string — knownIds holds raw ids while the group keys are always strings #8993 here — it is a renderer behaviour change with its own card.
    4. If you conclude the protocol is wrong to make groupBy optional, that is an objectstack spec card first and this card blocks on it — ⛔ never a local divergence defended afterwards.

    ⚠️ Zone-2: the anchors and claims above come from another dev's measurements and from a protocol sweep; re-derive them on your own base. ⭐ On this very card family, a dev's own probe refuted the review's mechanism and its own earlier claim — String(col.id) was true at the i18n lookup and false at the site that decides lane membership. That is the standard.

    Standing rules

    ⛔ Never edit a shared primary checkout (/home/user/objectui shallow + stale; /home/user/objectstack read-only). Clone fresh; report both HEADs and clean state at start and finish.
    ⛔ Never git stash; ⛔ never kill by name; ⛔ never mutate node_modules; ⛔ never edit content/docs/releases/; ⛔ never write the assignee or post a second Claim:.
    ⭐ An ablation touching packages/types needs a rebuild leg and dist markers read both ways — tsconfig.test.json sets an empty paths, so a skipped rebuild measures the old artifact and reads as "the mutation changed nothing".
    Capture every rc to a file before any pipe; read lint logs for the literal word error, excluding source context inside warnings.
    ⭐ Wait for CI to reach TERMINAL state before you report.
    ⛔ PR stays draft. GitHub content in English.


    Generated by Claude Code

  2. self-assigned this
    on Sep 10, 2026
  3. os-warren commented on Sep 10, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 8990,
      "status": "done",
      "branch": "claude/issue-8990-kanban-groupby-optional",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9021",
      "premise_still_valid": true,
      "summary": "ObjectKanbanSchema.groupBy is now OPTIONAL on both published faces (packages/types/src/objectql.ts and packages/types/src/zod/objectql.zod.ts), matching @objectstack/spec, which declares groupBy: z.string().optional(). Re-derived on my own base (2596b1b85): both faces did require it, so objectui refused a document the protocol accepts. I concluded the PROTOCOL IS RIGHT and filed no upstream spec card, on this repo's own corpus rather than deference: content/docs/utilities/data-objectstack.mdx documents a lane-less { type, dataSource } board, and plugin-list/src/ListView.tsx GENERATES the node as groupBy: laneField where laneField ends in an explicit || undefined. Both were recorded at objectui#7780 as readings excluded from objectui#7322's requiredness measurement; they are the evidence refuting it. This is a widening: an authored groupBy is still declared and enforced, and every flipped pin gained a CONTROL so optional cannot decay into undeclared.",
      "tests": "All on final head fe330a776, fresh clone, heavy runs through the shared verify lock. GREEN: @object-ui/types test 168 files/3341 passed; @object-ui/types type-check exit 0, 0 errors (covers tsconfig.test.json, where the type-level pins live); @object-ui/plugin-kanban test 48 files/276 passed; block-config.test.ts 43 passed; type-check plugin-kanban + plugin-list + plugin-view + app-shell all exit 0 / 0 errors. Gates (own verdict lines): check:control-bytes 'OK (scanned 7212 tracked text file(s))'; check:new-line-citations 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'; check:spec-symbols 'nothing cites a key its spec symbol does not declare'; check:designer-field-key-parity 'designer-field-key-parity: OK'; check:comment-mask-corpus within the pre-existing objectui#7882 residue (unchanged); check:changeset-presence '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check:governed-queue-guard --test 'NOT GOVERNED - none of 6 paths matched'. NOT MEASURED then remeasured: consumer type-checks first reported 36/48/43 errors, ALL TS2307 'Cannot find module @object-ui/components' = unbuilt dependency closure, not red; after pnpm --filter 'PKGNAME^...' build all four are 0. ABLATION (committed first, trap on EXIT INT TERM, restore from HEAD): both faces reverted to REQUIRED, source-only, no rebuild. On-disk proof both directions - TS removed-text count=0/injected count=1, ZOD removed-text count=0/injected count=1. ABLATED_EXIT=1, Test Files 3 failed, Tests 8 failed | 47 passed. Restore leg: git diff HEAD empty, git status clean, markers back at 1/1, then 55/55 green. DIST MARKER READ BOTH WAYS as required: dist/objectql.d.ts held groupBy?:string=1 / groupBy:string=0 BEFORE and STILL 1/0 DURING the mutation - unchanged, so the red came from SOURCE via vitest's @object-ui/types alias, not from a stale or rebuilt artifact (tsconfig.test.json's empty paths is the typecheck path, not this one). CI TERMINAL on fe330a776: 34/34 completed, 31 success + 3 skipped, ZERO failures - all four Test shards waited out to completion (shard 2/4 finished last, ~7 min after the first green reading).",
      "mcp_calls": "0 - REST probe returned 200 in-container, so every read and write went through repo-scoped REST (issue, comments, labels, PR create, check-runs); zero MCP GitHub calls for the whole run.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: KanbanImpl's isBoardEmpty = totalCardCount === 0 && boardColumns.length > 1, so a ZERO-lane or one-lane board never paints the objectui#8827 'No cards' live region. Pre-existing and independent of this card (the predicate does not read groupBy) and reachable today by a grouped board whose result is empty. Successor: the objectui#8827 empty-state family. It forced a rig change in my pin - one leg settles on the board region plus a paired control instead of the empty state, and says so at its site.",
        "noted, not filed: the six cross-file line citations in the groupBy docblock (:601 :613 :625 :640 :747 :865 into ObjectKanban.tsx) had ALL rotted - the same reads now sit ~370 lines lower. The dispatch's own anchors carried the same stale numbers, which Zone-2 re-derivation caught. I replaced my census with SYMBOL anchors rather than fresh line numbers. No card: check:new-cross-file-line-citations already owns this class and is deliberately report-only; note it scores this diff at '0 new citation(s)' because the bare :NNN form is not one it parses, which is exactly the form that rotted.",
        "noted, not filed: ListView's availableViews gate decides whether to OFFER the kanban view type from the view-level groupByField/groupField bindings, not from this node schema, so the widening does not make kanban newly offered anywhere. Verified, no change needed."
      ]
    }

    Generated by Claude Code

  4. os-warren commented on Sep 10, 2026

    @os-warren
    CollaboratorAuthor

    Seat dispositions — delivered, and it corrected two things this seat got wrong

    PM seat (domain:spec @ objectui, session session_01Jmxdo7bmeqCQHLSfmLVX9w), 2026-09-10. PR #9021, head fe330a7768b009bd0e568322bf7e4cea11250e22. CI terminal: 34/34, 31 success + 3 skipped, zero failures — all four shards waited out, shard 2/4 finishing ~7 min after the first green reading.

    ⛔ Correction 1 — my framing of what this card unblocks was too strong

    I wrote that making groupBy optional "is what makes the bare-string columns arm reachable." Measured, that is not right, and the dev's ablation shows why: re-requiring groupBy reddens only the contract legs of its pin; every render leg stays green.

    Mechanism: SchemaRenderer runs the structural validateSchema, never the zod mirror. ⇒ the renderer's string branch was always live for any document that reaches it unvalidated — ListView's generated node, or any host not running os check. What requiredness prevented was a schema-valid document getting there.

    ⭐ Now written into the pin file so nobody mistakes the render legs for guards on the declaration. That distinction is the whole difference between "the declaration gates this" and "the declaration describes this."

    ⛔ Correction 2 — my label instruction was written before the PR existed

    I said needs:contract-review was already on both carriers and ⛔ not to touch it. The PR did not exist when I wrote that, and it came up carrying only auto-labeler labels. The claim declares Clause-②: yes, so the standing rule owes the label on the PR side. The dev added it via the additive endpoint and read back comparatively — nothing stripped.

    That resolution is correct and I am not reversing it. ⭐ It surfaced the conflict and said "reverse it if you meant the issue label alone" rather than either silently obeying a stale instruction or silently ignoring it. That is the right handling of an order that has gone out of date.

    ⭐ It decided the protocol question on this repo's own corpus, not by deference

    Asked to judge whether the protocol is right to make groupBy optional, it did not answer "the principle says the protocol wins." It found the evidence:

    • content/docs/utilities/data-objectstack.mdx documents a lane-less { type, dataSource } board;
    • plugin-list/src/ListView.tsx generates the node as groupBy: laneField, where laneField ends in an explicit || undefined.

    ⇒ objectui was refusing a document objectui itself produces. And both readings were recorded at objectui#7780 as having been excluded from objectui#7322's requiredness measurement — they are the evidence that refutes it, sitting in the tree the whole time. No upstream spec card is owed.

    ⭐ objectui#8993 is strictly NARROWED by this change, asserted both ways

    I asked whether widening groupBy changes which of #8993's two paths runs. Measured: the double-bucketing sits after bucketCardsIntoColumns's !groupBy early return, and the unguarded id: opt.value lanes are built under if (schema.groupBy && …) — a branch a lane-less board cannot enter. ⇒ this card cannot widen #8993's blast radius; nothing about it was touched.

    ⭐ Zone-2 refuted the dispatch's anchors, and refused to mint new ones

    All six cross-file line citations in the groupBy docblock (:601 :613 :625 :640 :747 :865 into ObjectKanban.tsx) had rotted — the same reads now sit ~370 lines lower, and my dispatch carried the same stale numbers. It replaced the census with symbol anchors rather than fresh line numbers that would rot again.

    ⚠️ And it named a gate blind spot in passing: check:new-cross-file-line-citations scores this diff at "0 new citation(s)" because the bare :NNN form is not one it parses — which is exactly the form that rotted. The dev declined to file, on the grounds that the gate already owns the class and is deliberately report-only. ⭐ I am carrying it forward rather than closing it: a gate that is report-only about a class it cannot parse is not report-only about that class, it is silent on it. Whether that is worth a card is a judgement for the review round; I am not deciding it alone.

    Ablation precision worth keeping

    The dist marker was read both ways and came back unchanged (groupBy?:string = 1, groupBy:string = 0, before and during). ⇒ the red came from source, via vitest's @object-ui/types alias — ⛔ not from a stale or rebuilt artifact. The dev correctly distinguished which path was in play: tsconfig.test.json's empty paths is the type-check path, not the vitest one. That is more precise than the blanket "always add a rebuild leg" I put in the dispatch.

    Next

    needs:contract-review stays on both carriers; the in-seat clause-② review is dispatched against this head. ⛔ Nothing lands until a 同形 PASS record sits on it.


    Generated by Claude Code

  5. os-warren commented on Sep 10, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 8990,
      "status": "done",
      "branch": "claude/issue-8990-kanban-groupby-optional",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9021",
      "premise_still_valid": true,
      "summary": "REWORK ROUND after contract review returned VERDICT: REWORK on one blocking finding (R1, documentation). R1 fixed: four hand-written doc sites still asserted the pre-change contract and TWO of them named this very card as the pending change, so the PR closed the card while leaving the docs saying it was still open (AGENTS.md #2 is binding; no CI gate catches this class). schema-reference.md property table and columns callout, plugin-kanban.mdx callout, and plugin-kanban/README.md now all state optional AND the measured lane-less behaviour (bare-string lanes drawn and titled by RAW values vs the picklist LABELS a grouped board shows, zero cards, moves inert); both 'tracked as objectui#8990' sentences deleted. I also corrected TWO FURTHER README sites the review did not name (:48, :109) making the same requiredness claim, and made explicit in schema-reference.md that the record-source rule is separate from the lane key and unaffected. Non-blocking items also done: the two unqualified 'what made the arm live' sentences now carry the schema-valid qualifier; the #8993 wording is corrected from 'can only NARROW' to UNCHANGED / not widened; the six rotted line addresses are no longer restated even as history. The underlying contract change is unchanged from the reviewed head.",
      "tests": "All on final head fa93c7fc2, fresh clone, heavy runs through the shared verify lock. GREEN: @object-ui/types test 168 files/3341 passed; @object-ui/types type-check exit 0 / 0 errors; @object-ui/plugin-kanban test 48 files/276 passed; block-config.test.ts 43 passed. DOC GATES (this round's real risk surface): check:doc-snippets '638 of 638 block(s) judged, 0 failed' + 'Every covered documentation snippet compiles against the built types'; check:doc-types 'Every documented component type is registered'; check:doc-fences OK across 227 documents. Also check:control-bytes 'OK (scanned 7212 tracked text file(s))'; check:new-line-citations '0 new citation(s), enforcement report-only -> exit 0'; check:changeset-presence '6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'; check:governed-queue-guard --test 'NOT GOVERNED'. NOT MEASURED, then measured: check:doc-snippets first returned 'PRECONDITION NOT MET (exit 2) - The snippet program was NOT run: the packages it resolves against are not built' - read as not-measured, not red, and re-run only after the scoped 35-package build it prescribes. ABLATION NOW WITH THE TSC LEG the review found missing from round 1: baseline head tsc exit 0 / 0 errors; on-disk proof TS removed=0 injected=1, ZOD removed=0 injected=1; ablated vitest exit 1 = Test Files 4 failed, Tests 9 failed | 89 passed (98); ablated tsc exit 2 = 4 errors, exactly the four the review measured (7322:147 TS2344 _GroupByIsString, 7322:153 TS2344 _GroupByIsOptional, 7322:181 TS2741 lanelessLiteral, 7780:164 TS2741 LANELESS_BOARD_NOW_ACCEPTED). The vitest count matches the review's independent run exactly (its 8/47 over three files plus the block-config failure my set adds). Restore leg: git diff HEAD empty for both faces. CI TERMINAL on fa93c7fc2: 35 check runs, all completed, 32 success + 3 skipped, ZERO failures - all four Test shards waited out (shards 2/4 and 3/4 finished last).",
      "mcp_calls": "0 for this round as well (repo-scoped REST throughout: review comment read, PR PATCH, check-runs, report comment). Cumulative for the card: 0.",
      "open_questions": [
        {
          "question": "The review asks that the KanbanImpl empty-state gap be written onto objectui#8827 or filed, not only 'noted'. isBoardEmpty = totalCardCount === 0 && boardColumns.length > 1, so a zero- or one-lane board never paints the #8827 'No cards' live region; it is pre-existing and groupBy-blind, but this widening does let a schema-valid { type, objectName } board reach a silent blank that core validateSchema has no rule about. Your rework brief did not name it and you reserved the gate card for yourself, so I did not act unilaterally.",
          "options": [
            "A - I post a measurement comment on objectui#8827 recording the predicate, the newly reachable document class, and the rig consequence it forced in my pin",
            "B - I file a separate card (needs the dedupe search first) and link it from #8827",
            "C - You take it, as you took the citation-gate card"
          ],
          "recommendation": "A, because #8827 already owns this exact empty-state predicate and the finding is one paragraph of measurement rather than a work item of its own; B duplicates an owner, and C costs you a round-trip for something I have already measured."
        }
      ],
      "out_of_scope_findings": [
        "PROCESS FAILURE, mine, recorded rather than quietly fixed: I ran this round's ablation on an UNCOMMITTED tree, so its restore-from-HEAD leg reverted my own in-progress edits to objectql.ts along with the mutation. git status caught it; the edits were re-applied and COMMITTED before re-verification, and the measurements were unaffected because they tested the committed faces. The rule I broke is my own: commit before ablating, because the restore leg points at HEAD.",
        "boundary respected, with one clarification: this round's commit does not touch packages/types/src/zod/objectql.zod.ts, which PR #9017 holds. The PR AS A WHOLE necessarily does - that is the mirror half of the contract change, from round 1 - so the hold cannot be fully avoided by this card. Probed rather than assumed: my hunks are at :1216 and :1268, #9017's at :590, and git merge-tree reports a clean textual merge with no conflict.",
        "noted, not filed: I left one imprecise sentence in objectql.zod.ts untouched - it cites the mdx fragment among documents 'this validator refused', which is historically true but carries the same implication the review had me downgrade elsewhere. Fixing it means editing the file PR #9017 holds, the review did not name it, and its 'Owed before re-review' list does not include it. Flagging rather than touching; one line to fix once #9017 lands.",
        "noted, not filed: the citation-gate finding (scanner parses a continuation :NNN only within 2 lines of a numbered hit, so the six rotted addresses were invisible - silence about a class, not report-only about it) is measured and is yours to file, per your brief. Not changed here."
      ]
    }

    Generated by Claude Code

  6. os-warren commented on Sep 10, 2026

    @os-warren
    CollaboratorAuthor

    Release: MERGED — objectui#9021

    ObjectKanbanSchema.groupBy is optional on both published faces on main.

    Landing commit a4b723fab99b5e74917e03598382d25d15f487da · merged 2026-09-10T20:22:19Z by the queue · squash, single parent 152f0a700 · 10 files, +738 −90.

    Two readings, ⛔ never a single API field

    1. Timeline — added_to_merge_queue 20:04:25Z → removed_from_merge_queue 20:22:18Z → merged 20:22:19Z → closed 20:22:19Z.
    2. origin/main by content —
    git merge-base --is-ancestor a4b723fab99b5e74917e03598382d25d15f487da origin/main   rc=0
    git merge-base --is-ancestor fa93c7fc259025528afee61e5fe9ead3926ac3dc origin/main   rc=1   <- the control, and it FIRES
    

    ⚠️ The reversed probe is degenerate here — the landing commit is origin/main's tip, so --is-ancestor origin/main <landing> would answer rc 0 too and prove nothing. The control that actually fires is the pre-queue PR head fa93c7fc2…: it exists as a commit (cat-file -t → commit) and is not an ancestor of main, because the queue lands a different commit. That is the charter's own trap, measured rather than recited.

    Behaviour verification — ⛔ not commit presence

    reading on main @ a4b723fab value firing control
    packages/types/src/objectql.ts:3033 groupBy?: string; type: 'object-kanban'; in the same interface has no ? — the optionality detector distinguishes
    packages/types/src/zod/objectql.zod.ts:1286 groupBy: z.string().optional().describe(…) objectName: z.string() (no .optional()) at :229/:288/:383
    new pin plugin-kanban/src/__tests__/laneLessBoard-8990.test.tsx present, 16063 B a sibling path this PR did not add → No such file
    "tracked as objectui#8990" under content/ + packages/ 0 "objectui#8990" at all → 36, so the query is live, not dead

    ⚠️ One residue this landing creates, measured — it is NOT under content/ or packages/

    .changeset/8913-object-kanban-columns-declared.md:69 still reads "requiredness is tracked as objectui#8990" — now false, and it is a pending changeset, so it ships that sentence into packages/types/CHANGELOG.md at the next release. My first sweep missed it because it covered content/ and packages/ only; .changeset/ is neither. Folded into card objectui#9042, which already carries a sibling changeset correction and must land before @object-ui/types next publishes.

    Field reading

    Commit date 20:04:24Z → merged_at 20:22:19Z = 17m55s. Against the 17m31s–17m44s band from nine earlier landings and one 37m43s outlier: ⛔ a band is neither a ceiling nor a floor, and this point sits just outside it.

    Review history

    Round 1 REWORK (documentation: four doc sites still asserting the pre-change contract, two of them naming this very card as pending) → repair, which also fixed two sites the review had not named (plugin-kanban/README.md:48, :109) and added the missing tsc ablation leg, reproducing the reviewer's four errors exactly → round 2 VERDICT: PASS, record 5624643821, ceiling tier transcript-verified 95/95, independence NOT self-review.

    Card auto-closed by the PR's keyword (state_reason: completed); pm:dispatched stripped in this same act.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanefindingpackage: typesplugin: kanbanpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions