Repository navigation
finding(types/plugin-kanban): an authored kanban document can PASS safeValidateSchema and still render empty — the validator and the renderer honour different faces #7664
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 4, 2026 Maintainer ruling recorded — (a): for an authored
type: 'kanban'document the plugin dialect is authoritative.@object-ui/types''kanban'arm (type + Zod mirror) is rewritten to declare exactly the shapeObjectKanbanRendererreads (objectName/groupBy/cardTitle/cardFields/ cardbadges… — the 18-memberKanbanSchemaplugin-kanbanconsumes), andplugin-kanbanconforms to that declaration; theDeclarativeKanban*trio loses its only live consumer and retires under ADR-0049Director seat, summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um(GitHubos-warren), 2026-09-05. Provenance: maintainer, live PM chat, decision batch #41 (item 4, presented with the recommendation (a) and the explicit note that it reverses the "keep both faces" half of the objectui#6172 ruling), verbatim reply 「同意」. Premise: the card's table — the validator (safeValidateSchema, CLIvalidate/check) honoursDeclarativeKanbanSchema, the renderer registered for'kanban'honours the plugin'sKanbanSchema; PR #7643's census: all four registered kanban renderers consume the plugin dialect and none consumes the declarative one; objectui#6086 measured the consequence (a validated board renders empty).Ruled: (a). One contract for one type key: the
'kanban'arm ofComplexSchema→AnyComponentSchema→safeValidateSchemaaccepts what the registered renderer reads and refuses what it does not. Dependency direction stays correct —@object-ui/typesdeclares,@object-ui/plugin-kanbanimports and conforms (it already depends ontypes). TheDeclarativeKanbanSchema/DeclarativeKanbanColumn/DeclarativeKanbanCardtypes and their three Zod mirrors, renamed by PR #7643, are retired in the same change: objectui#6172's own stop condition was "if the renamed copy has no retained value, escalate" and the retained value it cited was precisely the validator arm — this ruling removes that value, so the retirement is the ruled outcome, not a reversal by a seat. Not taken: (b) (renderer rewritten to the staticcolumns/cardsdialect — four renderers rewritten and the data-bound board lost), (c) (both authorable, a new static renderer under a new key — a renderer nobody asked for; ④).Why (① ≥50%): a validator and a renderer bound to the same type key must read the same contract or a green
validatemeans nothing; (a) is the only shape where declared = rendered. ② every existing consumer is the data-bound board; the static board has zero. ③ "passes validation, renders empty" is the worst feedback an AI author can get; (a) turns it into a named refusal. ④ one dialect fewer.Execution:
domain:uiseat, M,packages/types(complex.ts,zod/complex.zod.ts,index.ts,zod/index.zod.ts,registry.ts) +packages/plugin-kanban(import the declared type instead of its local one; behaviour unchanged) + the zod-mirror-parity, handler-key and one-authority ratchets re-keyed; the docs pageapi/schema-reference.md's kanban example moves to the plugin dialect (#6973 is folded or closed by this — the seat decides and links it). Pins: anobjectName/groupByboard passessafeValidateSchema; acolumns/cardsboard is refused with a message naming the retired shape; the four renderers' props still type-check against the declared schema.Clause-②: yes⇒needs:contract-reviewon the PR, parked for the director. Changeset:@object-ui/typesminor with a BREAKING banner — the'kanban'validator arm's accept set is replaced and the sixDeclarativeKanban*exports (renamed by #7643 in the same release line) are retired, stated as the second step of that rename;@object-ui/plugin-kanbanpatch. Sequencing: after #7651 (ruled B in the same batch — no data ladder, sodata?: any[]stays a raw-row input in the declared shape) and clear of PR #7662'sSchemaRegistrychange (already merged per the card).State transition, same stroke:
needs-user-decision→pm:queue.finding·domain:uiunchanged. Ledger: objectstack #12708, batch #41. Related: objectui#6172 · #7645 · #7651 · #6086 · #6973 · PR #7643 · PR #7662.
Generated by Claude Code
Sequencing correction before this card is dispatched — PR #7662 has not merged
The ruling's Execution paragraph reads:
Sequencing: after #7651 (ruled B in the same batch …) and clear of PR #7662's
SchemaRegistrychange (already merged per the card).The first half holds — #7651 is closed
not_planned(2026-09-05T02:09:54Z). The second half does not. Read from the API just now:reading PR #7662 state open, draft, merged: falsemergeable_state behindgate carrying a second contract-review REFUSE (comment 5548793740) So
registry.tsdoes not yet carryBaseSchema & { type: 'kanban' }onmain; it still carriesDeclarativeKanbanSchema. An executor starting from the ruling's premise would edit a line that is not there yet, and would contend with #7662 on the same file.Surfaced by the independent contract review of #7662 (flag F5), which had to read both states to grade that PR — not a defect in the ruling's substance, only in one parenthetical about a sibling PR's status. The ruling's disposition is unaffected: either way, (a) re-points this entry.
Disposition from the
domain:uiseat: this card is NOT dispatched yet. It is held until PR #7662 lands, for two reasons that are the ruling's own:- Both changes edit
packages/types/src/registry.ts, and fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name #7662's outstanding remediation touches the comment at:201that (a) will rewrite. - fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name #7662's entry is the transitional state under (a). Landing them out of order means the executor writes the final state onto a file whose intermediate state is still in review, and the review record for fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name #7662 stops describing any tree that ever existed.
⛔ Not an indefinite park. #7662's refusal is wording-only and its remediation is dispatched; this card moves the moment that PR is merged. It stays
pm:queueso it is visible in the pool meanwhile.One more note for whoever executes it: the ruling folds or closes #6973 at the seat's discretion. #6973 was flagged earlier in this seat's log as possibly resting on a stale premise — re-measure it before folding, do not inherit the assumption.
Generated by Claude Code
- Both changes edit
✅ Hold lifted — PR #7662 has merged. This card is dispatchable now.
I held this card on 2026-09-05T02:39Z (comment 5548799174) because the ruling's sequencing note said PR #7662 was "already merged per the card" and it was not. That is now resolved by the fact, not by a re-reading:
PR #7662 merged 2026-09-05T07:14:56Z as
bc640ec56(merged: trueread from the API,Fixes #7645, card closed). It landed after three contract-review rounds; the third returned PASS.The exact state ruling (a)'s executor inherits — read from
main, not from a reportorigin/mainbc640ec56,packages/types/src/registry.ts:'kanban': BaseSchema & { type: 'kanban' };
⛔ So do not start from
DeclarativeKanbanSchema. The entry is the transitional form, and (a) re-points this one line at the declared type.The #7662 contract review enumerated the rest of the inheritance, and judged it does not pre-empt any part of (a):
- the one type line above;
- the
BaseSchemaimport atregistry.ts:9, which becomes unused after the re-point — a tidy, not a gate (noUnusedLocals: false,@typescript-eslint/no-unused-varsiswarn); - the comment block at
registry.ts:187-217, whose "⛔ Do not restore … without moving the renderer's dialect into a layer this package may depend on" names (a)'s route rather than forbidding it; - both pin test files, which import
DeclarativeKanbanSchemaand therefore retire with the trio — disclosed in their own docblocks. The reviewer flagged_KeyKeptas worth carrying forward.
Two numbers in the ruling that the executor should NOT inherit
- The ruling says "the 18-member
KanbanSchema". The measured body count is 19 (AST census, controls lit). Re-derive rather than trusting either number. - The ruling folds or closes objectui#6973 at the seat's discretion. finding(docs):
schema-reference.md'sKanbanSchemaexample mixes both dialects —cardsfrom the plugin copy withcolor/draggablefrom the types copy, so it matches no declaration #6973 was flagged in this seat's log as possibly resting on a stale premise — re-measure it before folding, do not inherit the assumption.
Also cleared since the ruling was written
- [Decision] Should
ObjectKanbanget a real record-source ladder? —KanbanSchema.dataisany[], so it has nodatabinding to resolve #7651 — closednot_planned(ruled B in the same batch). The fence it named is gone. - finding(types): three more type names are declared twice with disagreeing shapes —
FormField,MarkdownSchema,KanbanSchema— and one doc assertsFormFieldis declared once #6172 — closedcompletedby PR refactor(types)!: rename the declarative Kanban trio, leaving the bare names to plugin-kanban #7643. Its "keep both faces" half is what (a) reverses; the ruling says so explicitly.
Card stays
pm:queue, unassigned.Clause-②: yesper the ruling, so whoever takes it owes a draft PR withneeds:contract-reviewon both carriers, and aminorchangeset with a BREAKING banner.
Generated by Claude Code
Claim: PM loop round 25 —
domain:uiexecution seat
Session:session_01KbJQ1y1J12nZxYzFWhP8Q3
Branch:claude/issue-7664-kanban-plugin-dialect-authoritative
Worktree:objectui-issue-7664
Domain:domain:ui
File surface:packages/types/src/complex.ts,packages/types/src/zod/complex.zod.ts,packages/types/src/index.ts,packages/types/src/zod/index.zod.ts,packages/types/src/registry.ts,packages/plugin-kanban/**, the zod-mirror-parity / handler-key / one-authority ratchets,content/docs/api/schema-reference.md(stop on breach; explain in the report)
Container & model: M,mode:subagent,model: fable—node scripts/pm/dispatch-gates.mjs --tierrun this round returns no path-derived mandate; the fable tier is the clause-② CONTENT mandate, and the gate's own line calls itself a floor, never a clearance.
Clause-②: yes
Serial constraints cleared: Two live contentions, both declared rather than waited on.#7731(claude/issue-7433-parity-population-derived) edits onlypackages/types/src/__tests__/zod-mirror-parity.test.ts+ a changeset — the exact ratchet this card must re-key, and it is rewriting how that population is DERIVED.#7726(claude/issue-7530-predicate-envelope-declared) editspackages/types/src/index.ts,zod/index.zod.ts,zod/base.zod.tsand the same parity test. Neither is a blocker: merge the base branch, ⛔ never rebase.⚠️ If #7731 lands first the parity population becomes derived rather than listed — re-read that file before writing any ledger count, do not port a hand-count. Also in flight onpackages/types:#7732(complex.tsChatMessage region — a different interface from this card's kanban arm),#7730(zod/base.zod.tsonly). Cleared by measurement, not memory:#7651closednot_planned,#7662mergedbc640ec56— the ruling's own sequencing note said #7662 was already merged when it was not, corrected on this card at comment 5548799174; it has since become true.registry.tsonmainnow reads'kanban': BaseSchema & { type: 'kanban' }.
Generated by Claude Code
os-dev-report
{ "issue": 7664, "status": "done", "branch": "claude/issue-7664-kanban-plugin-dialect-authoritative", "pr": "https://github.com/objectstack-ai/objectui/pull/7743", "premise_still_valid": true, "summary": "Ruling (a) executed: @object-ui/types' 'kanban' arm now declares the plugin dialect (KanbanSchema / KanbanColumn / KanbanCard / CardTemplate / ColumnWidthConfig, member for member the plugin's shape) with five Zod mirrors applied by safeValidateSchema, SchemaRegistry['kanban'] names it, plugin-kanban/src/types.ts re-exports it (behaviour unchanged), and the six DeclarativeKanban* exports plus both 7645 pins retire under ADR-0049 with a 7664 pin on each side. The retired dialect's inert keys (board draggable, column color — zero read sites) are ?: never tombstones refused by name naming DeclarativeKanbanSchema; onColumnAdd / onCardAdd carry over; the parity, handler-key and docs-retired ratchets are re-keyed (parity on top of PR #7731's derived population, merged in: EXPECTED_MIRROR_PAIRS = 159); schema-reference.md moves to the plugin dialect; changesets types minor (BREAKING banner) + plugin-kanban patch. Re-derived numbers: 19 live members + 3 tombstones (ruling said 18; `type` is the difference); objectui#6973 was already closed completed at 2026-09-05T00:43Z, so nothing to fold — referenced, not closed. Deviations to review: the pin 'a columns/cards board is refused' was read as 'a board in the retired dialect is refused' because the ruled shape itself declares columns[].cards[] (the catalog's static boards render); the two 'could not run' gates (readme-exports, doc-snippets) need a whole-workspace build and are left to CI. Dispatch inheritance held: the card carried assignee os-sam and pm:dispatched at dispatch; the label set on the card was never written by this seat. Landed since BASE and merged (no rebase): #7726, #7730, #7731, #7732, #7686.", "tests": "All at bd1fc7111 (head; union re-run after the merge commit). Lock VERDICT command-exit 0 for: pnpm --filter @object-ui/types build; tsc -p packages/types/tsconfig.test.json --noEmit; tsc -p packages/plugin-kanban/tsconfig.json --noEmit; tsc -p packages/plugin-kanban/tsconfig.test.json --noEmit => ALL_TSC_GREEN (--listFiles: both test projects include the new pin files, the three re-keyed ratchets, complex.ts, complex.zod.ts; plugin project reads types/dist/complex.d.ts + registry.d.ts). pnpm exec vitest run --maxWorkers=2 over 10 files (types 7664 pin, zod-mirror-parity, handler-keys-6124, docs-retired-7340, phase2-schemas; plugin 7664 pin, registration; catalog kanban-column-cards-6939, safe-validate-corpus-6318; scripts one-authority-6273) => 'Test Files 10 passed (10)', 'Tests 389 passed (389)'. Ablation A: registry.ts 'kanban': KanbanSchema -> CalendarViewSchema, mutation proved on disk (anchored count 1->0 / 0->1), tsc types test project exit 2 with exactly 1 error in kanban-plugin-dialect-authoritative-7664.test.ts; restore via git checkout HEAD -- (absolute path, trap), proved by git hash-object == HEAD blob 3b5ce94b and empty git diff HEAD --stat; no rebuild needed, the project reads src. Ablation B: complex.zod.ts draggable refusal -> z.boolean().optional(), mutation proved (1->0 / 0->1), vitest on the pin exit 1 '2 failed | 9 passed' (the two refusal tests); restore proved by hash == HEAD blob b5a341dd and empty diff; no rebuild in this leg, vitest resolves @object-ui/types through the source alias so dist was not on the path. Gates exit 0 with verdict lines: check:phantom-deps, check:doc-types, check:doc-fences, check:control-bytes (6309 files), check:unreferenced-sources, check:spec-symbols, check:published-dist, check:element-data-source-declaration, check:sdui-registration-pins, check:self-import, check:published-tsconfig-exclude, scripts/check-doc-links.mjs ('Links are valid across 17 scan roots'), check-changeset-presence (2 changesets for 14 source files of 2 packages), check-changeset-no-major. ESLint narrowed: eslint --format json over the 16 changed lintable files => 0 errors, 48 warnings (no-explicit-any on members carried verbatim); population evidence: repo lint scripts are 'eslint .' per package (no --no-inline-config — that spelling reddens 4 untouched files on main), JSON-counted run over the touched directories 356 files 0 errors, eslint.config.js has no parserOptions.project / type-checked config so untouched verdicts cannot move. NOT MEASURED locally: check:readme-exports ('312 self-import(s) could not be judged … run pnpm build first') and check:doc-snippets (exit 2 'I could not run') — both need every package's dist; left to CI. CI status at report time: in_progress (draft PR just opened).", "mcp_calls": "2 — two directed search_issues dedupe queries (both non-empty, self-validating); every other read/write went through git, the public web payload, or repo-scoped REST (issue create, PR create, label add, comment)", "open_questions": [ { "question": "The ruling's refusal pin says 'a columns/cards board is refused', but the ruled shape (the plugin's KanbanSchema) itself declares columns[].cards[] and the catalog's static boards render every card. Which reading should the contract review hold?", "options": [ "A — as implemented: refuse the retired dialect at its distinguishing inert keys (board draggable, column color) by name; retired card keys (labels/priority/dueDate/…) stay accepted because a card is an open record and those are legitimate field names", "B — additionally refuse the retired card keys by name (would refuse real records whose fields are named priority/dueDate/labels; bucketCardsIntoColumns pushes raw records into lanes)", "C — refuse every static columns/cards board (contradicts the declared type, breaks the two catalog entries and kanban-column-cards-6939)" ], "recommendation": "A, because it is the only reading where the mirror refuses what the renderer does not read and accepts what it does; B refuses data, C refuses the declaration" } ], "out_of_scope_findings": [ "filed as #7742: the ruled KanbanSchema carries three zero-read members (allowCollapse, cardTemplates, columnWidths) and ObjectKanban reads an undeclared titleField fallback for cardTitle; root README's kanban example authors columns[].value/label/color, a column shape no renderer reads — each an ADR-0049 enforce-or-remove decision on the shape this PR declared (no assignee, no label)", "already open, not re-filed: #7322 (the sibling object-kanban arm requires groupField with zero read sites while the same renderer reads groupBy) and #7712 (plugin-kanban reads schema.filter undeclared) — both surfaced by the same read census; referenced from #7742 and the PR body" ] }
Generated by Claude Code
Contract review (clause ②), fresh and full after the R1 REFUSE — PASS — PR #7743 at head
1a5915f2Director seat, summon #15,
session_01TezFG8ZMrNH6n5VTNpPpdH(os-zhuang), 2026-09-05T16:2xZ, on the maintainer's instruction 「然后执行契约复审」. Tier fuse read byget_sessionthis session:session_context.model=last_served_model=CONTRACT_REVIEW_TIER. This is a new review of the whole 25-file remediated diff at1a5915f2, not a re-read of the REFUSE (5552425067) — that verdict's findings were used only as an attack list. Readings are this seat's own, taken from the PR head fetched asrefs/pull/7743/headand from objectuiorigin/main737037f2.Implemented-by:
session_01KbJQ1y1J12nZxYzFWhP8Q3's os-dev round (dev reports 5552339519, 5552854017 on this card; branchclaude/issue-7664-kanban-plugin-dialect-authoritative)
Reviewed-by:session_01TezFG8ZMrNH6n5VTNpPpdH① Derived judgments — accept set and public face
# claim this seat's reading verdict R1 onCardClickis a declared runtime slot on the'kanban'arm: callable on the TS face, refused by name on the mirrorPR head packages/types/src/zod/complex.zod.ts:152:onCardClick: handlerKeyRefusal('onCardClick', 'runtime-slot', 'Card click handler')besideonCardMove(:151) andonQuickAdd(:155) — a declared refusing arm, soBaseSchema's.passthrough()no longer keeps the value. TS facecomplex.ts:onCardClick?: (card: KanbanCard, event?: unknown) => void. The widening the REFUSE measured is closed at the source, not only at the probe.correct R1-disp runtime-slot rather than retired objectui origin/mainplugin-kanban/src/index.tsx:194forwardsonCardClick={schema.onCardClick}in the same block as the two kept slots;ObjectKanban.tsx:151declares anonCardClickprop and:938-940its substitute calls the authored one. An authoredonCardClickon atype: 'kanban'document runs ⇒ live on every channel measured;retiredwould have declared a live key dead.correct 2 Ledger re-keyed: RUNTIME_SLOT44 → 45,ALL_SITES66 → 67,RETIRED22 unchanged;KeepsFunction<KanbanSchema['onCardClick']>type assertionDiff of handler-keys-json-refusal-6124.test.ts:toHaveLength(45)/toHaveLength(22)/new Set(ids).size).toBe(67), theonCardClickrow re-keyed toKanbanSchema, and theExpect<KeepsFunction<…>>line present. The dev's ablation (?: neverback in ⇒ TS2344 at that line + TS2322 inzod-mirror-parity) shows the type instrument is lit.correct 3 The 'kanban'arm declares the plugin dialect (19 live members +onCardClick+ 3 tombstonesdraggable/onColumnAdd/onCardAdd; columncolor?: never);registry.ts'kanban': KanbanSchema; sixDeclarativeKanban*exports removedMirror hunk read: KanbanSchema = BaseSchema.extend({...})withdraggable: retiredDeclarativeKanbanKey(...),onColumnAdd/onCardAddas'retired'arms;index.tsexportsKanbanCard,KanbanColumn,KanbanSchema. Whole-name removal for the six exports is right — a whole exported name has no carrier to tombstone, and the retirement is the ruling's own outcome (objectui#6172's stop condition).correct 4 No remaining consumer of the retired names git grep DeclarativeKanbanon objectui / cloud / hotcrmorigin/mainoutside the PR's own files: two historical.changesetentries and one comment line inscripts/check-doc-links.mjs:508(no code reference). Zero live importers.correct 5 plugin-kanban/src/types.tsbecomes a re-export of the five declared types; behaviour unchangedFiles list −218/+31 on that file, ObjectKanban.tsximport becomesimport type; the plugin's public type names are preserved through the re-export.correct 6 Refusal pin re-read as A (refuse draggableand columncolor, notcolumns[].cards[])The plugin dialect declares columns[].cards[]andbucketCardsIntoColumnspushes raw records intocards, so refusing card field names would refuse the declaration itself; A executes the ruling's principle (accept what the registered renderer reads). The REFUSE reviewer reached the same answer independently.correct ② semver
@object-ui/typesminor with a BREAKING banner (validator arm's accept set replaced; six exports retired as the second step of the #7643 rename) and the accept-set bullet for all five handler arms added in remediation;@object-ui/plugin-kanbanpatch (re-exports; the "member for member" claim corrected to "plus four").check-changeset-no-majorgreen. Matches the ruling (5548643216).③ Boundary flags
- REFUSE's non-blocking (a)(b)(c): all three addressed in the remediation — plugin changeset wording, tombstone JSDoc citing prong 2 of the discriminator,
schema-reference.mddocumentsonCardClickagain and the 7340 control grows to 7. bindcorrection (a liveBaseSchemamember, not an undeclared read) carried; finding(types,plugin-kanban): the ruledKanbanSchemacarries three zero-read members (allowCollapse,cardTemplates,columnWidths) and the board reads an undeclaredtitleField— enforce-or-remove on the shape objectui#7664 declared #7742 extended with five re-measured rows by comment, not a new card.- Systemic finding filed as objectui#7753 (a hand-written handler-key ledger cannot see a deletion; the new derived guard covers 3 of 67 sites). Correctly its own card.
- Dev question on member count (18 vs 19): re-derived by AST, pinned; the ruling's number was short by
type. Confirmed.
Evidence and landing checks
- CI on
1a5915f2: 29 success / 3 skipped / 0 red (individually enumerated by the seat at 15:43Z; re-read here).mergeable_state: cleanagainst base83c77dc3. ⚠️ Read before the queue runs:origin/mainhas moved past the merge-base with threepackages/types/srccommits (feat(types): refusechartTypeon a chart series by name, pointing attype#7737chartTyperefusal, docs(types): align two layout@defaultdocblocks with the renderer fallbacks #7736 docblocks, feat(types)!: retire ChatbotSchema.displayMode as an ADR-0049 tombstone; remove the Display Mode control and seed (objectui#7654) #7739ChatbotSchema.displayModeretirement). No textual conflict, but the count ratchets (EXPECTED_MIRROR_PAIRS = 159, the 7340 docs census 26,RETIRED22) may need re-deriving in the merge-queue build if any of those three moved a count. That is a CI/ratchet matter for the dispatching seat, not a contract finding; it does not hold this PASS.- Governed:
check-governed-merges --teston the 25 paths ⇒ 0 hits, ordinary queue landing.content/docs/releases/: 0.Fixes #7664is correct — this card's whole scope lands with the PR.
Clearing, same stroke:
needs:contract-reviewcomes off this card and PR #7743 with a provenance comment citing this PASS; then ready + auto-merge (SQUASH).
Generated by Claude Code
- REFUSE's non-blocking (a)(b)(c): all three addressed in the remediation — plugin changeset wording, tombstone JSDoc citing prong 2 of the discriminator,
Landing blocked at the queue — merge lap owed on PR #7743 (conflict with #7758),
domain:uiseatDirector seat,
session_01TezFG8ZMrNH6n5VTNpPpdH, 2026-09-05T16:4xZ. Measured on the timeline and the tree, not inferred:- PR feat(types): the
kanbanarm declares the plugin dialect; the DeclarativeKanban* trio retires (objectui#7664) #7743added_to_merge_queue16:14:25Z →removed_from_merge_queue16:17:28Z;mergeable_statenowdirty. git merge-tree --write-tree --name-only origin/main origin/pr-7743(objectuiorigin/main4dfdcc3c): one conflict,packages/types/src/__tests__/zod-mirror-parity.test.ts. Cause: test(types): watch the mirror-wider-than-declared direction with a fifth parity ledger #7758 (test(types): watch the mirror-wider-than-declared direction with a fifth parity ledger) landed after this PR's merge-base83c77dc3and edits the same ledger this PR re-keys (EXPECTED_MIRROR_PAIRS = 159,KnownDrift,SPEC_DERIVED_PAIRS). Also on main since the merge-base: fix(types): bind the ListView exportOptions mirror to the spec field; drop the ListView casts (#6956) #7763 (ListViewexportOptionsmirror), feat(types)!: retire ChatbotSchema.displayMode as an ADR-0049 tombstone; remove the Display Mode control and seed (objectui#7654) #7739 (ChatbotSchema.displayModetombstone), feat(types): refusechartTypeon a chart series by name, pointing attype#7737 (chartTyperefusal), docs(types): align two layout@defaultdocblocks with the renderer fallbacks #7736 — any of which can move a count this PR pins. This is the ratchet risk the PASS (5553091448) named.
Handover to the
domain:uiseat (session_01KbJQ1y1J12nZxYzFWhP8Q3) — a patch round on the original dev, not REWORK: mergeorigin/mainintoclaude/issue-7664-kanban-plugin-dialect-authoritativewith a merge commit (⛔ no rebase / amend / force-push), resolve the parity-ledger conflict by composing both edits (#7758's fifth ledger + this PR's kanban re-key; re-deriveEXPECTED_MIRROR_PAIRSfrom the population, do not pick a side), re-run the ratchet instrument (tsc -p packages/types/tsconfig.test.json --noEmit) and the types/plugin-kanban vitest, and push. The card stayspm:dispatched; nothing else on this card changes.Contract review after the lap: the head will move by a merge commit plus a conflict resolution in a test ledger. Post the new head here and this seat re-reads the resolution and extends the PASS to it (a merge-only move with a ledger re-derivation is an extension, not a new review); only then does auto-merge go back on. ⛔ Do not re-arm before that comment.
Generated by Claude Code
- PR feat(types): the
Nudge — the merge lap on PR #7743 is still owed (
domain:uiseat,session_01KbJQ1y1J12nZxYzFWhP8Q3/ assignee os-sam). At 17:32Z the branch tip is still1a5915f2(15:18Z),mergeable_stateunresolved, no comment since the handover 5553249289 (16:4xZ). The PR sits out of the merge queue until the conflict inpackages/types/src/__tests__/zod-mirror-parity.test.ts(vs #7758) is composed on a merge commit and pushed. Nothing else blocks landing: contract PASS 5553091448 on record, CI green on the current head, governed test 0 of 25. When the new head is pushed, post it here; this seat extends the PASS and re-arms auto-merge. Director seat,session_01TezFG8ZMrNH6n5VTNpPpdH; reported to the maintainer.
Generated by Claude Code
Contract review (clause ②) — PASS 5553091448 extended to head
b115f08d; gate cleared on both carriersDirector seat, summon #15,
session_01TezFG8ZMrNH6n5VTNpPpdH(os-zhuang), 2026-09-05T19:2xZ. The re-hang by thedomain:uiseat (5554184339) was the right act under 「head 后移或无结论才重挂」; this is the extension it was waiting for, read from the tree, not from the reports.Delta
1a5915f2→b115f08d, read in full:commit content reading 2702b741,724a7a5e,b5de2f20three merges of origin/main(up to2fcefb95)git diff origin/main origin/pr-7743shows the PR's own delta unchanged: the parity file's net change vs main is exactly the reviewed re-key —DeclarativeKanban*→KanbanCardSchema/KanbanColumnSchema/KanbanSchema(+CardTemplateSchema,ColumnWidthConfigSchema) in imports,MIRRORS,SPEC_DERIVED_PAIRS;KnownDrift`KanbanSchema: onCardMoveb115f08dzod-mirror-parity.test.ts−4 / +0: the twoWiderThanDeclaredrows'complex.zod.ts#DeclarativeKanbanColumnSchema': 'cards'and'complex.zod.ts#DeclarativeKanbanSchema': 'columns'with theirSCHEMA-NODEdoc linesCorrect and the only correct remedy: both rows key on pairs this PR retires, so on the merged tree they fail assertionWiderLedgerKeysAreRegistered(TS2344) — a sequencing consequence of #7758 and this PR landing in order, not a contract change. Re-keying them to the new pairs was measuredWiderOf = never(the drift they recorded does not exist on the plugin-dialect arm), so there is nothing to carry. Coverage lost: none. The ledger's own remedy 4 ("a key DISAPPEARED — correct or delete the entry").Ratchet evidence on the new head:
tsc -p packages/types/tsconfig.test.json --noEmitexit 0 (was exit 2 with exactly the one TS2344 before the deletion); lit control — mutatingCarouselSchema: 'items'turns two assertions red;RUNTIME_SLOT45 /RETIRED22 / ids 67 /EXPECTED_MIRROR_PAIRS159 re-measured onb115f08d, all equal to the figures the PASS was taken on. CI onb115f08d: 29 success / 3 skipped / 0 red, individually enumerated by the seat and re-read here.mergeable_state: clean.Two readings worth carrying past this PR (the seat's, confirmed here): (1) the merge lap's first stop-and-report was correct — a row in another PR's ledger is a fence, and the ruling to delete came from the dispatching seat with the measurement, not from the dev's preference; (2) vitest green while tsc red — the parity ledger is compile-time;
tsc -p packages/types/tsconfig.test.jsonis the instrument, never the vitest run.Verdict: PASS extended to
b115f08d. Every derived judgment in 5553091448 holds on this head; the accept set, public face and semver are unchanged. Clearingneeds:contract-reviewfrom this card and PR #7743 in the same stroke; then auto-merge (SQUASH). If the head moves again before the queue takes it, this extension does not carry — re-hang per the rule.Implemented-by:
session_01KbJQ1y1J12nZxYzFWhP8Q3(dev reports 5553898255, 5554084984)
Reviewed-by:session_01TezFG8ZMrNH6n5VTNpPpdH
Generated by Claude Code
落地确认:PR #7743 已合并。本卡由
Fixes自动关闭。三条判据全过,读数如下。domain:uiPM 席(session_01KbJQ1y1J12nZxYzFWhP8Q3,round 6)。① 合并读数 —— 读
merged字段本身项 读数 mergedtruemerged_at2026-09-05T19:39:34Zmerge_commit_sha2af1fa7c03d5edf311872870e896435436ccc5f2merged_byos-zhuang(经合并队列)diff 25 files, +1470 / −601 本卡 closed_at 2026-09-05T19:39:36Z⛔ 没有把
mergeable_state当合并证据(合并后它读unknown)。② 队列轨迹 —— 读 timeline 事件,⛔ 不读
auto_mergeadded_to_merge_queue 16:14:25Z os-zhuang removed_from_merge_queue 16:17:28Z github-merge-queue[bot] ← MERGE_CONFLICT 弹出 added_to_merge_queue 19:24:57Z os-zhuang ← PASS 延展后重新武装 merged 19:39:34Z中间那次弹出正是三轮 merge lap 的起点:
main在此期间移动了九个提交,最后一次是 PR #7782(同样改zod-mirror-parity.test.ts),逼出第三次 mergeb5de2f206,之后才是 PM 裁定 A 的 4 行删除b115f08d0。③ 关闭列表 —— 只关了本卡
PR 正文里的闭合关键词只有一个:
7664。正文另提到 17 个编号(#6124#6172#7712#7753等)全部走Refs/ 散文,没有一个带闭合关键词。⇒ 无误伤。⚠️ 后续载体存活核对:10 个全部 open卡 状态 #7744 · #7745 open, pm:queue#7747 open, pm:dispatched(本轮已派)#7750 · #7751 open, pm:queue(security,p2)#7753 · #7756 · #7765 open —— ⚠️ 三张此前一个pm:*标都没有,见下#7786 open, pm:dispatched(本轮已派)#7787 open, pm:blocked(Blocked-by: #7652)⚠️ 本席自己漏的三张,已补#7753 / #7756 / #7765 此前是半状态 —— 卡活着,但对任何按
pm:*扫描的派发视图不可见(#7753、#7756 一个标都没有;#7765 只有finding)。三张都是本会话自己立的,漏标是本席的账。已补:- finding(types): the #6124 handler-key ledger is hand-written by name, so it cannot see a DELETED key — 64 of its 67 sites have no read-site derivation #7753
findingtestspackage: typesdomain:uip2pm:queue—— finding(types): 28 zod-mirror keys are declaredz.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 的 handler-key 台账按名字手写,因此看不见一个被删掉的键。BaseSchema是.passthrough(),键离开 arm 不是被拒绝,而是不再被判断、值被保留;67 个站点里 64 个没有读点派生。⇒ 与本卡同一个家族的下一次,且每一道门都会是绿的。 - finding(plugin-detail): RelatedList.lookupLabelResolution renders inside a waitFor predicate — a 50ms lookup-batch delay exhausts the V8 heap and kills the whole vitest worker #7756
findingtestsplugindomain:uip2——RelatedList.lookupLabelResolution在waitFor谓词里渲染;lookup 批延迟多 50ms 就耗尽 V8 堆并杀死整个 vitest worker(tests 0ms,什么都没报出来)。 - The network-escape guard prescribes a stubGlobal + unstubAllGlobals pair that races a component re-read started after the test body returns #7765
findingtestsdomain:uip2—— 网络逃逸守卫自己打印的Fix:建议(stubGlobal+unstubAllGlobals)正是造成 flaky(app-shell): RecordDetailView.approvalDeclaredActions trips the network-escape guard intermittently — theafterEachstub teardown races a still-pending effect #7439 那次 flake 的配对。⇒ 一道门在教人写出它自己会抓的东西。
⇒ 三条判据全过,后续载体零丢失,三张半状态已修复。
Generated by Claude Code
- finding(types): the #6124 handler-key ledger is hand-written by name, so it cannot see a DELETED key — 64 of its 67 sites have no read-site derivation #7753
MERGED confirmed. PR #7743 merged 2026-09-05T19:39:34Z (squash
2af1fa7c); this card auto-closed byFixes #7664at 19:39:36Z. Read on objectuiorigin/main(abcb6218):packages/types/src/zod/complex.zod.ts:141declaresKanbanSchema = BaseSchema.extend({...})as the'kanban'arm, withonCardClickas aruntime-slotrefusal;DeclarativeKanbanSchemasurvives only as the two documented mentions (one doc paragraph, theretiredDeclarativeKanbanKeyrefusal-message helper) — no export. Contract review PASS 5553091448, extended tob115f08din 5554221020.pm:dispatchedremoved in the same stroke. The systemic follow-up (a hand-written handler-key ledger cannot see a deletion) stays open as objectui#7753 with thedomain:uiseat. Director seat,session_01TezFG8ZMrNH6n5VTNpPpdH; ledger objectstack#12708.
Generated by Claude Code
- added a commit that references this issue
on Sep 30, 2026
Filed by the
domain:uiPM seat from the objectui#7645 flight. The dev seat surfaced it and deliberately did not file it, because it sits against objectui#7651's open maintainer decision and objectui#6172's settled ruling — filing across a live maintainer decision is the PM's call. Recorded by the repository itself rather than inferred.The hazard
For an authored node of type
'kanban', two different types are authoritative depending on who is asking:safeValidateSchema, i.e. what the CLI'svalidate/checkcommands acceptDeclarativeKanbanSchemapackages/types/src/complex.ts's own docblock states it is the'kanban'arm ofComplexSchema→AnyComponentSchemaObjectKanbanRenderer, registered for the'kanban'key@object-ui/plugin-kanban's ownKanbanSchemapackages/plugin-kanban/src/index.tsxThe two are structurally unrelated dialects: the declarative face carries
draggable, columncolor, cardlabels/assignees/priority; the plugin face carriesobjectName,groupBy,cardTitle,cardFields, cardbadgesand thirteen more.⇒ A board can pass validation and render empty. An author runs
objectui validate, gets a green result, ships it, and the renderer reads none of the keys the validator just blessed.objectui#7645 was a false claim in a type map with zero indexed accesses — nothing consumed it, so nothing broke. This is different: both consumers are live, both run on real authored documents, and they disagree. A green
validateis exactly the signal an author would trust.Not a duplicate of the neighbours
SchemaRegistry['kanban']stop naming either face. That removed a false type claim; it did not reconcile the two consumers.ObjectKanbanshould gain a real record-source ladder. Adjacent, different axis.The decision needed
Which face is authoritative for an authored
kanbandocument? Everything else follows from it:⛔ Not dispatchable until that is answered — every remedy is a different piece of work, and two of the three move a published surface.
Refs: objectui#7645 (origin) · objectui#6172 (both faces kept) · objectui#7651 (open decision) · PR #7643 · PR #7662.
Generated by Claude Code