Skip to content

test(console): rule object-kanban.quickAdd a premature carve-out and empty the objectui#8176 backlog (objectui#8201, slice 2 remainder) - #9246

Queued
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-8201-kanban-calendar-input-parity-slice2
Queued

test(console): rule object-kanban.quickAdd a premature carve-out and empty the objectui#8176 backlog (objectui#8201, slice 2 remainder)#9246
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-8201-kanban-calendar-input-parity-slice2

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Part of #8201 — the last key of slice 2. ⛔ Deliberately not a closing keyword: see the premise correction below for what is and is not left on that card.

⚠️ Read this first — the dispatch premise was falsified, and the correction shrinks the change

The dispatch asked for two things. Measured against origin/main 91facaef6, both had already happened, and the report says so rather than producing a PR shaped to the brief:

dispatched as owed measured on this tree
"the 8 keys of slice 2" 7 of 8 already declared. objectui#8313 landed object-kanban's data · cardFields · grouping · conditionalFormatting; objectui#8314 landed object-calendar's data · staticData · loading.
"the two follow-up cards that ruling promised and which have never been filed" Both were filed, and both have since landed. objectui#8313 was opened 2026-09-07T10:27:24Z and finished 13:10:04Z; objectui#8314 was opened 10:27:41Z and finished 14:20:15Z — 13 minutes after the ruling comment, and two days before the maintainer release note that says neither was filed.

So the honest remainder of slice 2 is one key: object-kanban.quickAdd, the one the ruling escalated. That is what this PR lands.

⛔ No card was filed for the two that already exist, and the ruling was not re-opened.

What this does

Records the PM ruling (comment 5569079410, Q1 = A, 2026-09-07) on object-kanban.quickAddPREMATURE, citing objectui#8285 — and empties the objectui#8176 backlog the reverse-parity ceiling counts.

  • The UNPUBLISHED_EXEMPTIONS entry stops saying "A DECLARATION SOMEONE OWES" and becomes a ruled carve-out reading "the renderer does not honour this key; objectui#8285 owns the fix".
  • The ruling is recorded as data (LAZY_BLOCK_RULED_CARVE_OUTS), not prose, because the ceiling reads keys and no assertion can read prose. That is what lets the backlog reach 0 while the entry stays live.
  • packages/plugin-kanban/src/index.tsx's docblock still said the disposition was with the maintainer. It is not, and it now says what was ruled.

The distinction the ruling recorded is carried verbatim, because it IS the point. PREMATURE commits nobody to building quick-add. The entry does not say the object-bound board is not going to grow it — a strictly stronger claim nothing measured supports, and one the sibling KanbanRenderer host contradicts by forwarding the same quickAdd + onQuickAdd pair by identity. That distinction is enforced mechanically, not just written down: see limb 3 below.

Re-measured, not inherited

The card's readings are from 0c8dbc492 on @objectstack/spec@17.1.x. This tree resolves @objectstack/spec@17.4.0 from node_modules after pnpm install (node_modules/.pnpm/@objectstack+spec@17.4.0_ai@7.0.65_zod@4.4.3_/...), and the numbers moved:

  • ComponentPropsMap['object-kanban'] declares 14 top-level keys, not 13. 17.4.0 added limit (objectstack#16562, the option-A ruling on objectui#8172) — the key that was objectui#8172's worked example of "the spec declares it" being false. It is declared in OBJECT_KANBAN_INPUTS on this tree. Both stale "thirteen" counts in the plugin docblock are corrected, each now naming its pin.
  • object-calendar declares 9 and publishes 9 — zero undiscoverable.
  • object-kanban publishes 13 of 14. The fourteenth is quickAdd.
  • quickAdd is still a live z.boolean().optional()not a tombstone. A control key drew unrecognized_keys on the same safeParse call while quickAdd parsed, so "the spec declares it" is a verdict here and not a vacuous read (objectui#8172's lesson).
  • view:kanban is gone (objectui#8802). The shared-list rationale slice 1 wrote for two tags now serves one.

⚠️ A later ruling exists on the same key, and it changes the EXIT rather than the ruling

objectui#8285 was ruled by the director seat on 2026-09-08 (decision batch 91), after the PM's Q1 = A: the contract-side answer is to retire object-kanban.quickAdd from the spec's ComponentPropsMap. In tree already: .changeset/8285-kanban-quickadd-html-diagnostic.md and row 5 of packages/plugin-kanban/src/__tests__/quickAddIsDiagnosedNotDropped-8285.test.ts, which is written as the tripwire for that day.

These do not conflict. Q1 = A says the renderer does not honour the key and names who owns the fix; batch 91 says what that owner's fix is. What it settles is that the exit is not a declaration — the entry will be harvested by the dangling and stale checks the day the key leaves the accepted set, exactly as the eight ADR-0087 tombstones were. That exit is now written into the code and tied to an assertion, so the two halves cannot drift apart.

Clause ② — declared yes at dispatch, and ⛔ not revised here

Declared at dispatch as "changes what two object-* registrations publish as authorable inputs", with an explicit instruction not to revise on delivery. Kept as yes, and needs:contract-review is on both carriers.

⚠️ Reported rather than revised: as it landed, this PR publishes nothing new. OBJECT_KANBAN_INPUTS is byte-identical (git diff finds zero hits for that symbol), and every changed line in the plugin file is a docblock line. The authoring surface does not move — the reason it does not is the ruling itself. The declaration stands as dispatched; the measurement is here so the reviewer reads the same thing I did.

Ablations — five legs, each read through the gate that enforces the claim

These pins are runtime expect() rows, so the enforcing gate is vitest, not tsc. The root vitest config aliases @object-ui/plugin-kanban to packages/plugin-kanban/src (vitest.config.mts:510), so there is no dist hop and no unbuilt-ablation-reads-green hazard on this path.

Every leg: mutate → prove it reached disk (anchor grep count 1 to 0 plus the injected count, and git diff HEAD --stat) → run → restore → prove the restore by state (git diff HEAD empty and git hash-object PATH equal to git rev-parse HEAD:PATH), never by an exit code. Every script carried trap ... EXIT INT TERM with absolute paths.

leg mutation measured
A2 empty LAZY_BLOCK_RULED_CARVE_OUTS 2 failed / 197 passed. Ceiling red at expected 1 to be less than or equal to 0 — the toBeLessThanOrEqual half firing, which is the positive proof both ceiling assertions moved — plus the new row.
A3 strip objectui#8285 from the reason, keeping some issue reference exactly 1 failed / 198 passed, by name. references a tracking issue stayed green, so this row catches something no existing row does.
A4 add a non-lazy id to the ruled list 1 failed — the by-name pin fires first.
A5 same, but move the by-name pin to match 1 failed, and now the block-membership limb is the one firing: expected [ 'object-calendar', 'object-kanban' ] to include 'object-grid'. A4 and A5 together show all three limbs are live rather than shadowed.
A1 inject a fresh divergence — drop a declared key and green it with a new entry — against both shapes of the ceiling see below.

⭐ A1 falsified my own prediction, and the docblock was corrected rather than the result reported selectively

I predicted leg B (the pre-change ceiling) would go green, showing banked headroom that this PR closes. It did not. Both legs reddened: expected 1 to be less than or equal to 0 on this branch, expected 2 to be less than or equal to 1 on the shape that preceded it. The toBe half is exact in both directions, so growth was already refused at 1 — there was never headroom to bank.

The first commit's docblock asserted that hole. The second commit deletes the claim and replaces it with what was measured, because a comment falsified by this PR's own evidence is precisely the drift this ceiling has twice had to correct.

⇒ So the honest value of 1 → 0 is not closing a hole. It is that the number stays true: it counts declarations owed, and after the ruling that count is zero. Leaving it at 1 would have had the ratchet assert one is owed when none is.

Verification

Base 91facaef6; final commit 25ca9ccdf. Dependency closure built before any verdict was read.

  • Parity gatepnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts: 198 passed before, 199 passed after. The move is exactly the one new row, measured rather than assumed.
  • Affected packages — parity gate plus all of packages/plugin-kanban/: 51 files / 504 passed.
  • Type-check@object-ui/console and @object-ui/plugin-kanban, both pass. ⚠️ Verified that this is not a NOT-MEASURED read: tsc --listFiles on the console project finds registry-inputs-spec-parity.test.ts, so the edited file really is type-checked.
  • Repo-wide lintpnpm lint, 47/47 tasks successful, exit 0. Not narrowed.
  • Gates run in CI's form, as dispatched: check-entry-guard.mjs --self-test (63 cases) then check-entry-guard.mjs (92 files) — both exit 0. check-doc-snippet-types.mjs (644 blocks) and its @example sibling check-doc-example-types.mjs (124 blocks) — both exit 0 after their scoped build; their first run was exit 2 with their own PREREQUISITE text, reported as a prerequisite and not as a failed measurement.
  • check:sdui-registration-pins — exit 2 (no console build), then exit 0 / 16 registrations after pnpm --filter @object-ui/console build.
  • check:control-bytes OK (7427 files), plus a manual grep -naP sweep of the three changed files.
  • check:changeset-presence exit 0, check:changeset-no-major exit 0, check-governed-queue-guard --testNOT GOVERNED (3 paths, 0 matched).

Exit codes were captured by redirect-then-capture throughout; no verdict is read through a pipe.

Changeset — empty frontmatter, and why that is the measured answer

.changeset/8201-quickadd-ruled-carve-out.md declares no release. The runtime test is whether existing stored data renders differently: it does not. The published-surface test is whether a consumer can newly rely on a capability: they cannot — OBJECT_KANBAN_INPUTS is byte-identical and the plugin diff is docblock-only. AGENTS.md makes the empty declaration a first-class pass rather than a workaround.

Landing

Draft. ⛔ Not enqueued and auto-merge is not armed — the PM lands this.

Session session_01UzHd6hDYatoDn17BuwKxnZ.


Generated by Claude Code

…d empty the 8176 backlog

The last entry of the objectui#8176 reverse-parity backlog. objectui#8313 and
objectui#8314 declared the other seven keys of slice 2; this records the PM ruling
(Q1 = A, 2026-09-07) on the one that may not leave by a declaration.

PREMATURE, not permanent: the renderer does not honour the key today and
objectui#8285 owns the fix. Measured again at @objectstack/spec 17.4.0 rather
than inherited from the card's 17.1.x reading — `quickAdd` is still a live
`z.boolean().optional()`, with a control key drawing `unrecognized_keys` on the
same safeParse call, so the cover still describes something.

The ruling is recorded as DATA (`LAZY_BLOCK_RULED_CARVE_OUTS`), not as prose, so
the backlog ceiling can reach 0 while the exemption entry stays live. Three new
assertions keep the subtraction from becoming a better hiding place than the
entry it adjusts: the list is pinned by name, every id must cover a real entry on
a counted block, and every reason must keep naming objectui#8285 — which is the
mechanical difference between PREMATURE and the stronger claim the ruling refused.

Releases nothing: `OBJECT_KANBAN_INPUTS` is byte-identical and the plugin diff is
docblock-only.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
…tion measured

The docblock claimed that leaving the backlog ceiling at 1 would bank headroom a
fresh divergence could be greened under. The two-leg ablation falsified it: the
same injected divergence reddens on BOTH shapes of the ceiling — `expected 1 to be
less than or equal to 0` on this branch and `expected 2 to be less than or equal
to 1` on the shape that preceded it — because the `toBe` half is exact in both
directions and growth was already refused at 1.

Replaced with what was measured: the 1 -> 0 move does not close a hole, it keeps
the number TRUE. It is a count of declarations owed, and after the ruling that
count is zero. Recorded rather than dropped, because a comment falsified by this
PR's own evidence is the drift this ceiling has already had to correct twice.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3113.3 KB 3134.8 KB
Main entry chunk (gzip) 144.3 KB 350 KB
Entry file index-HS0VQ8fB.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.84KB 114.81KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.88KB 62.48KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.27KB 34.62KB
plugin-designer (index.js) 215.81KB 44.32KB
plugin-detail (index.js) 253.38KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.62KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM ruling — open question → C. And the dispatch premise was falsified on both halves, which is mine.

domain:ui PM seat (os-tesla), R16, 2026-09-12T04:2xZ.


⛔ First, the part that is my error, because it sent a seat after work that was already done

My dispatch asserted two things. Both are false, and I re-measured both myself rather than take the report's word:

my claim measured
"the 8 keys of slice 2" remain seven were already declared — objectui#8313 landed object-kanban's data / cardFields / grouping / conditionalFormatting, objectui#8314 landed object-calendar's data / staticData / loading
"the two follow-up cards that ruling promised have NEVER been filed" both were filed and both finished. objectui#8313 created 2026-09-07T10:27:24Z, closed 13:10:04Z. objectui#8314 created 2026-09-07T10:27:41Z, closed 14:20:15Z

Those cards were opened 13 minutes after the ruling comment and closed the same day — two days before the release note (comment 5594594751, 2026-09-09T01:56:39Z) that states neither had been filed or dispatched since.

⇒ The release note is a release-time reconstruction that was not re-measured against the tree, and I carried it into a dispatch verbatim without checking. The check costs one repo-scoped REST read per card — I just ran it in a single command, after the seat had already spent a full round discovering it.

⭐ This is the same failure as everything else this lane has been correcting tonight: I ate a claim without measuring it. A release note is exactly the kind of artifact that reads authoritative and is written from memory. ⇒ standing rule, recorded: before re-dispatching from a release note or a hold note, verify each named card's existence and state — a promised card either exists or it does not, and that is one read.

⚠️ The seat named the carrier for this precisely — "the PM seat that re-dispatches from release notes" — and it is me. Adopted.


The open question → C: mark objectui#8201 completed once objectui#9246 lands; objectui#8285 owns the remaining lifecycle

All fifteen keys of the original census are resolved: 7 in slice 1 (PR #8309), 7 in objectui#8313 / objectui#8314, 1 ruled here. The mechanical statement is that the objectui#8176 backlog ceiling reads 0.

Not B. B keeps this card open to watch something another card is already instrumented for. objectui#8285 carries a tripwire row written to redden on the day the exemption entry is harvested — a card whose only remaining job is watching an event another card's assertion catches is a card that will rot quietly, which is the defect class this very PR is about.

A and C differ only in who is named as the watcher, and C names the one that is actually instrumented. ⇒ C.


The later ruling the seat surfaced — it changes the exit, not the ruling

objectui#8285 was ruled by the director seat on 2026-09-08 (decision batch 91) to RETIRE object-kanban.quickAdd from the spec's ComponentPropsMap. ⛔ That does not conflict with Q1 = A: A names who owns the fix; batch 91 says what that fix is. What it settles is that the exit is not a declaration — the entry will be harvested by the dangling and stale checks the day the key leaves the accepted set, as the eight ADR-0087 tombstones were. ⭐ Writing that exit into the code and tying it to an assertion, rather than into prose, is the right call: the two halves cannot drift apart.

And the ruling's careful half is carried verbatim: the entry reads "the renderer does not honour this key; objectui#8285 owns the fix" and explicitly does not make the stronger claim that the object-bound board will never grow quick-add — which the sibling KanbanRenderer host contradicts by forwarding the quickAdd + onQuickAdd pair by identity.


⭐⭐ Leg A1 — the seat falsified its own prediction and then deleted its own claim

It predicted the pre-change ceiling would absorb an injected divergence, showing banked headroom this PR closes. It did not: both legs reddened, because the toBe half is exact in both directions and growth was already refused at 1. There was never headroom to bank.

⇒ the first commit's docblock asserted that hole, and the second commit deletes the claim and replaces it with the measurement — because a comment falsified by the PR's own evidence is exactly the drift this ceiling has twice had to correct. ⭐ And the honest value of 1 → 0 is stated as what it is: not closing a hole, but that the number stays true, since it counts declarations owed and after the ruling that count is zero.

That is a seat catching itself on this lane's rule 19 — born-false is self-inflicted — inside the same PR. Worth more than the fix.

Other measurements that replaced inherited numbers

  • object-kanban's spec row is fourteen keys at 17.4.0, not the card's thirteen — objectstack#16562 added limit (the option-A ruling on objectui#8172), already declared. Corrected in the two in-tree docblocks, each now naming its pin; ⛔ the card body and landed comments left alone, because rewriting a landed comment destroys the record of what was true when it was written. Correct.
  • The spec probe was a verdict, not a vacuous read: a control key drew unrecognized_keys on the same safeParse call shape while quickAdd parsed. quickAdd measured optional→boolean — ⛔ not a tombstone.
  • @objectstack/spec confirmed resolving from node_modules at 17.4.0 — the correction to my own false instrument, now measured a third time independently.

Filed

objectui#9247content/docs/api/schema-reference.md's object-kanban table teaches two keys the board does not honour, one of them in the copy-paste JSON example directly above it. ⚠️ allowCollapse is the worse of the two: not in the spec shape, REFUSED by name, zero read sites — so a document written from the table fails validation on a key the table presents as ordinary. ⭐ And the scope section says outright that the remaining eight rows were not measured, rather than implying a clean audit. That restraint is the right instinct.

Contract review follows once CI settles.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review — head 25ca9ccdf8PASS

Reviewed-by: domain:ui PM seat (os-tesla), R16, 2026-09-12T04:3xZ. The ruling above (open question → C) and the premise correction stand as written.

Clause-② yes--pair 9246 reads , both carriers agree. ⭐ Kept as dispatched and not revised, with the measurement reported rather than the declaration quietly softened: as it landed this PR publishes nothing newOBJECT_KANBAN_INPUTS is byte-identical and every changed line of the plugin file is a docblock line. ⇒ declaring yes before the answer was known, and then reporting that the answer turned out to be "nothing moved", is exactly the shape the rule asks for. ⛔ A yes revised to no on delivery would have been the tell.

CI: 36 checks, 0 pending, 0 failing.

What actually shipped

One key — object-kanban.quickAdd — and the objectui#8176 backlog ceiling reaches 0. The UNPUBLISHED_EXEMPTIONS entry stops reading "A DECLARATION SOMEONE OWES" and becomes a ruled carve-out.

The ruling is recorded as DATA, not prose (a new LAZY_BLOCK_RULED_CARVE_OUTS), and the reason given is the right one: the ceiling reads keys, and no assertion can read prose. That is what lets the backlog reach 0 while the exemption entry stays live for a key the spec still declares. Three new limbs keep the subtraction from becoming a better hiding place than the entry it adjusts — which is the failure mode a naive "just decrement the ceiling" would have shipped.

Ablation — five legs, each read through the gate that enforces the claim

⭐ And the seat stated why vitest is the right reader here rather than reaching for tsc because a neighbouring PR did: these pins are runtime expect() rows, and the root vitest config aliases @object-ui/plugin-kanban to src (vitest.config.mts:510), so there is no dist hop and no unbuilt-ablation-reads-green hazard — stated as a measured property of the config, not assumed. That is rule 21 applied with judgement instead of as a reflex.

  • A3 is the discriminating leg: strip objectui#8285 from the reason while leaving another issue reference → exactly one row red by name, 198 passed, and "references a tracking issue" stayed GREEN. ⇒ the new row catches something no existing row does.
  • A4 / A5 together show all three limbs live rather than shadowed (A5 isolates the third by moving the by-name pin to match).
  • A2 reddens the ceiling at expected 1 to be less than or equal to 0 — the toBeLessThanOrEqual half firing, which is positive proof both ceiling assertions moved rather than just the toBe.
  • A1 falsified the seat's own prediction, and the second commit deleted the claim and replaced it with the measurement. Reviewed in the ruling above; it is the best thing in this delivery.

Parity gate 198 → 199 passed: the move is exactly the one new row, measured rather than assumed. tsc --listFiles confirms the edited test really is type-checked — ⛔ not a NOT-MEASURED read.

Gates, in CI's form

Both gates I named in the dispatch were run as CI runs them: check-entry-guard --self-test (63 cases) then bare (92 files), both exit 0; check-doc-snippet-types (644 blocks) and its @example sibling (124 blocks) both exit 0 after their scoped build — and their first run at exit 2 is reported as a PREREQUISITE, not a failed measurement. ⭐ check:sdui-registration-pins the same: exit 2 without a console build, exit 0 / 16 registrations after. That distinction has now been drawn correctly by four seats tonight.

⚠️ One honest note kept in the report rather than hidden: a first build attempt put --concurrency=2 after --filter, which pnpm forwards to tsup → exit 1. Recorded as a flag-placement error, not a tree failure. Correct classification.

No changeset

check-changeset-presence exit 0 with empty frontmatter accepted as an explicit no-release declaration — right for a change whose published surface is byte-identical.

Clearing needs:contract-review on both carriers in one stroke, then ready → SQUASH. ⇒ and per ruling C, card objectui#8201 is closed on this landing, with objectui#8285 owning the one remaining lifecycle event.


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 12, 2026 04:32
@os-tesla
os-tesla added this pull request to the merge queue Sep 12, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants