fix(plugin-calendar): type ObjectCalendar at the published object-calendar schema, and declare the calendar container - #9471
Conversation
…endar schema `ObjectCalendarComponentProps.schema` was `ObjectGridSchema | CalendarSchema` — a grid's schema plus a plugin-local interface absent from this package's barrel. Neither arm is the schema of the element this renderer is registered as, so of the fifteen keys it reads off the node, four were declared on neither arm and eight on exactly one, and every one of them needed a cast to read. Measured with the TypeScript checker (`getPropertyOfType`, never a grep): `ObjectCalendarSchema` already declared eleven of the fifteen. - `ObjectCalendarComponentProps.schema` is now `ObjectCalendarSchema`. Breaking for a host that passed an `object-grid` node or a bare `type: 'calendar'` literal; neither is a node this renderer is registered for. The shape matches every sibling widget, `plugin-map` included, which is registered under two tags with one published props type exactly as this package is. - `ObjectCalendarSchema.calendar` is declared on both published faces, derived from the spec's `CalendarConfigSchema` plus objectui's own `allDayField` and kept `.passthrough()` — value validation where there was none, no new refusal. - The `dateField` / `endField` alias rungs are retired: no producer, fixture, test or doc writes them, and the spec refuses both by name in both positions. - Two pins re-anchored off spellings this change had to move (objectui#8832), and the new spec dependency registered in `SPEC_DERIVED_PAIRS`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
…on baseline `Test (shard 4/4)` reddened on the objectui#6273 ratchet: the baseline listed a `CalendarSchema` collision between `packages/plugin-calendar/src/ObjectCalendar.tsx` and `packages/types/src/form.ts`, and the previous commit resolved it by deleting the plugin-local declaration. The ratchet refuses to let a resolved entry sit in the baseline, so the entry comes down with the fix. This NARROWS the exemption set; nothing is added, loosened, skipped or disabled. Verified it went stale because of the fix and not because the detector lost sight of either file: with the collision put back on disk under a trap — proved to have reached disk by hash, restored by hash equality against the HEAD blob — the same gate goes green at 11/11, so it still walks both files and still pairs them. Its own `scans a population that has not collapsed` row passes in both readings. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
⭐ Two cards this diff touches besides objectui#8651 — neither of them closed hereFound after the body was written, so recorded here rather than by rewriting a correct body. objectui#7311 —
Its triage comment rules the remedy verbatim, and agrees with what this PR did: 「这张卡不需要动 types,只需要让 props 指向它」. It also cites the two precedents this diff follows — objectui#5018 ( ⇒ this PR implements its remedy in full, and deliberately carries no closing keyword for it. Two reasons: whether a card closes is a landing decision and not this seat's, and objectui#7311 still owns a docs half this diff does not do — it was filed out of the
objectui#9473 — filed by this card for the Generated by Claude Code |
Contract reviewIndependent contract review of ① The claim sweepPopulation bound — mechanically derived, and the splitter self-tested on a known input before it was pointed at the tree.
The four surfaces restate the same propositions repeatedly (the PR body, the changeset, the source docblocks and the test titles say the same things in different words). Deduplicating on proposition, not on wording, the 314 units collapse to 45 distinct checkable propositions. I measured all 45. 41 hold. 4 do not. No proposition was accepted on the PR's own say-so; every accept/refuse reading below fired both a known-accepted control and a known-refused nonsense control. The 4 that do not hold① ⛔ The ② ⛔ The cast count is contradicted by the TypeScript checker. changeset, line 9: "twelve of the fifteen keys it reads off the node were undeclared on the union and had to be read through a cast" Measured at the merge-base by compiling one read per key against the real union, with a nonsense control on the same call:
The rule the PR states — "a union member is declared only when EVERY arm declares it" — is not how TypeScript resolves a union whose arm carries ③ ⛔ Public-surface change #4 claims it does. Measured off the BUILT declaration barrels, with both controls:
④ ⛔ "no conforming author could write them and no write door could store them" is FALSE for the nested position. The flat half is true. The nested half is not, because The sentence is literally true of the named schema and false of the element's own props schema, which is the face an author is actually held to. The 41 that hold (measurement named for each load-bearing one)Card premises re-derived with the checker: 4-on-neither-arm ( The renderer reads fifteen, not twelve ✅ — re-derived with my own TypeScript-AST census (property accesses whose object expression is the identifier
Barrel / shadowing ✅ — ② Contract correctnessDoes this move a published accept set, and does any part move PAST the installed
|
| gate | unbuilt reading | classification | my verdict |
|---|---|---|---|
check:readme-exports |
exit 1 — exportSymbols: found 27, floor is 400, 27 export symbol(s) read from 1 of 40 tracked package(s) (36 unbuilt), and the gate's own text: "Something upstream of the judgement broke … or the packages were never built." |
NOT MEASURED | Correct. PRECONDITION NOT MET. After pnpm run build: exit 0, 3372 export symbol(s) read from 37 of 40 … (0 unbuilt) |
check:sdui-registration-pins |
exit 2 — "❌ No console build to weigh at apps/console/dist/assets. This is exit 2, not a pass … a run with nothing to read has measured nothing." | NOT MEASURED | Correct. The gate self-declares it. After build: exit 0, All 16 registration(s) … present |
check:node-esm-load |
exit 1 — provenance refusal, and the refused entries name another worktree: @object-ui/fields ← /home/user/objectui-issue-8651/packages/fields, plus "turbo shares one cache across every worktree of a checkout" and "only 0 entries evaluated … This run proved nothing" |
NOT MEASURED | Correct, and I reproduced the mechanism independently: a plain pnpm build in my fresh worktree reported 43 cached, 43 total >>> FULL TURBO, i.e. replayed artifacts it did not build. With --force-build (4m18s, 0 cached): Provenance leg: 37 of 37 gradable entries were built by this tree, Load leg: 34 of 39 … evaluated, exit 0 |
None of the three is a red rounded to "not measured", and none is a genuine not-measured rounded to green.
Test-quality notes (not FAIL items)
- The census instrument in the new pin re-derives both sides every run and masks comments first — that is the right shape, and the
CONTROL: both halves of the row above can failrow does fire on both spellings. Good. ⚠️ object-calendar-record-source-7313.test.ts: the re-anchor's stated control is "the slice really is the call and not the whole file", but the assertions areexpect(call.length).toBeLessThan(src.length)andexpect(call.endsWith(')')).toBe(true). A paren match that ran away to the file's last)satisfies both. The control is weaker than its comment claims. (The current behaviour is correct; the guard is not.)
Gates, suites, lint, CI — everything I ran, exit codes as read
All 22 gates the PR names: 0 each (note check:changeset-presence / check:changeset-no-major are not npm script names — pnpm run -s exits 1 on the missing script; node scripts/check-changeset-presence.mjs and …-no-major.mjs are 0 and 0, which is the real reading).
git ls-tree -r origin/main --name-only .github/workflows/ | wc -l → 38 ✅, but I re-derived the gate invocations from those 38 files and the list omits at least 20 of them, several directly reachable by this diff: check:changeset-claims, check:vi-mock-override-shape / -specifiers / -inherit (the new pin uses vi.mock with an override), check:test-path-roots (a new file under __tests__), check-type-check-coverage, check-lint-coverage, check-changeset-fixed, check-changeset-overwrite. I ran every one of them and every one is 0, so the bound holds in outcome — but "derived from the 38 workflow files" is not what happened. Full list run by me, all exit 0: spec-symbols · readme-exports · element-data-source-declaration · sdui-registration-pins · handler-key-reads · metadata-write-doors · unreferenced-sources · self-import · phantom-deps · side-effects-array · published-dist · published-tsconfig-exclude · entry-guard · doc-types · doc-examples · doc-example-readers · doc-snippets · doc-fences · doc-example-ids · doc-links · doc-expression-carriage · spec-floors · esm-specifiers · control-bytes · new-line-citations · changeset-presence · changeset-no-major · changeset-claims · changeset-fixed · changeset-overwrite · type-check-coverage · lint-coverage · test-path-roots · vi-mock-override-shape · vi-mock-specifiers · vi-mock-inherit · action-forward-parity · action-ref-convention · designer-field-key-parity · icon-record-names · i18n-keys · i18n-drift · i18n-designer-parity · eager-closure · eager-locale-catalogues · docs-route-closure · upstream-port-parity · bash32-floor · lockfile-integrity · pre-install-import-graph · prompt-keys · skills-paths · skill-examples · skill-eval-tokens · governed-queue-guard · merge-queue-head · required-check-set · shell-escape-residue; node scripts/check-node-esm-load.mjs --force-build 0.
Suite, from the repo root: pnpm exec vitest run packages/plugin-calendar/ packages/types/ scripts/__tests__/one-authority-per-exported-name-6273.test.ts --reporter=verbose → exit 0, Test Files 223 passed (223), Tests 4549 passed (4549), apps/console 0 occurrences. Matches the PR exactly.
Lint: pnpm exec eslint . --no-inline-config --format json linted 4973 files in my worktree; 5 of those are my own probe files (zz-*), so 4973 − 5 = 4968 — the PR's figure, exactly. eslint.config.js declares neither project nor projectService (positive control: rules occurs 13×). Of the branch's 8 changed files, 7 are linted and each reports 0 errors (the eighth is .changeset/olive-buckets-scream.md, which eslint does not lint) — so "all 7 files this branch changes" understates the branch by one file; the substance holds. 79 files carry pre-existing errors (95 total) and none is touched by this branch.
CI at the reviewed head, from commits/40c825c7ff…/check-runs — total_count 36, returned 36 (not truncated): 33 success, 3 skipped, zero non-green; the three skipped are dependabot, Test (coverage), Test (coverage shard ${{ matrix.shard }}/4); all four Test (shard N/4) are success. Matches the PR exactly. No label was added or removed by me; the PR carries needs:contract-review and is still draft.
NOT MEASURED
content/docs/releases/as a non-vacuous constraint — the path has zero tracked files in this repository, so a zero there proves nothing about the guard. Substituted control:content/(203 tracked files), 0 touched.@objectstack/spec's intent forComponentPropsMap['object-calendar'].calendarbeingz.unknown()— I measured that it isz.unknown().optional()at 17.4.0, but whether that is deliberate or a spec-side gap is objectstack's call, not readable from this repo.- Whether any out-of-repo consumer authors
calendar: { dateField }— unknowable from here. The in-repo producer path is measured and sufficient. turbo run build --filter=!@object-ui/siteas "43 of 43 tasks" built — my plain run reported43 cached, 43 total >>> FULL TURBO, i.e. replayed, not built. The--force-buildrun (0 cached, 43 total) is the reading I trust, and it is 0.e2e/live,performance-budgetandhalf-state-patrolworkflows — not runnable in this environment (no live backend / no baseline artifacts). Not rounded to green; CI's own check-runs cover them at the head sha.
Implemented-by: the domain:spec @ objectui developer seat (author of 5c2ff8413d / 40c825c7ff)
Reviewed-by: an independent contract reviewer holding the domain:spec @ objectui PM seat — did not write, push, label or touch this branch; worked only in throwaway worktrees at 40c825c7ff and e3cb47624e
FAIL
What must change:
- Do not retire the
dateField/endFieldrungs on the evidence given — there IS a producer.packages/plugin-list/src/ListView.tsxcase 'calendar':(lines 2725–2733) spreads...(schema.options?.calendar || {})and...(schema.calendar || {})onto the emittedobject-calendarnode, and objectui's publishedListViewSchemaacceptscalendar: { dateField }/calendar: { endField }(measured: ACCEPT, with a wrong-type control that REFUSES). A view authored that way renders today and draws "Calendar configuration required" at this head (measured on both trees, with a canonical-spelling control that refuses on neither). Either (a) drop the retirement from this PR and keep the two rungs, or (b) fix the producer first — resolve the aliases inListView.tsx's calendar branch the wayresolveTimelineDateBindingalready does (s.startDateField ?? s.dateField) so no retired spelling ever reaches the node — and land that ahead of, or in, this diff, with a pin covering theviewType: 'calendar'+calendar: { dateField }path end to end. - Correct the cast count on both faces. Change the changeset's "twelve of the fifteen keys it reads off the node were undeclared on the union and had to be read through a cast" and
ObjectCalendar.tsx:84–87's "FOUR … and EIGHT …, so every one of them needed a cast to read" to the measured figure: seven of the fifteen could not be read without a cast (allDayFieldcalendardataendDateFieldnavigationstartDateFieldstaticData); the other five one-arm keys (colorFielddateFielddefaultViewendFieldtitleField) compile off the union becauseObjectGridSchema's[key: string]: anysupplies them to the union's property set. State that mechanism, since the PR's premises table already measures the index signature and the conclusion contradicts it. - Either publish
ObjectCalendarBlockConfigor stop claiming it is published. It is absent from@object-ui/types(685 exports) and@object-ui/types/zod(214 exports), withObjectCalendarSchemaas the positive control in both. Add it to theexport { … } from './objectql.zod.js'block inpackages/types/src/zod/index.zod.ts, or delete public-surface claim Add default props to all components to prevent collapse in designer #4 from the PR body. - Fix the nested half of the spec-refusal claim.
ComponentPropsMap['object-calendar'].calendarisz.unknown().optional(), notCalendarConfigSchema, and it acceptsdateField,endFieldand a nonsense key alike (measured, with a strict-CalendarConfigSchemacontrol that refuses all three). RewordObjectCalendar.tsx'sgetCalendarConfigdocblock and the changeset so "no conforming author could write them and no write door could store them" is not asserted of the nested position. - Restate the gate-list bound as the assertion it is, or derive it. The 38-file count is right; the list is not the set those files invoke. I re-derived that set and found ≥20 further gates, including
check:changeset-claims,check:vi-mock-override-shapeandcheck:test-path-roots, all of which this diff can reach. Either name the derivation command that produced the 22 (and show it), or say plainly that the 22 are a chosen subset and list what was left out. - Strengthen the re-anchored control in
object-calendar-record-source-7313.test.ts.expect(call.length).toBeLessThan(src.length)andexpect(call.endsWith(')')).toBe(true)are both satisfied by a paren match that ran away to the file's last). Bound the slice instead — e.g. assert it contains no newline-delimitedfunction/constdeclaration, or that its length is under a small ceiling, or thatsrc.indexOf('resolveRecordSourceConfig(', at + 1)is outside it. - Fix the file count in the lint narrowing. The branch changes 8 files, 7 of which eslint lints; "all 7 files this branch changes were linted" reads as a census over the change set and is one file short. Say "all 7 lintable files of the 8 changed".
Generated by Claude Code
Seat decision — the contract review is ADOPTED (FAIL), and defect 1 is a live behaviour regression, not a prose defect
The record is same-form, so it is adopted whole
⛔ Adopted verbatim. The seat does not soften a FAIL and does not trade the numbered list down. Defect 1 re-checked by the seat before adoption, because a FAIL on a regression is expensive⇒ the two limbs the regression rests on are confirmed independently: objectui's own published ⭐ And the reviewer did not stop at the census. It mounted
That is a behaviour measurement end to end, not an inference from a schema. ⛔ It is why this FAIL is not negotiable: a user-visible calendar stops rendering. What the seat is NOT doing
One lesser finding the seat is recording rather than requiringThe gate list was asserted, not derived — 38 workflow files is right, but the reviewer re-derived the invocations and found ≥20 reachable gates omitted ( The other checks, for the record③ at this head was green — ⛔ The NextAll numbered items go back to the implementing dev as written. ⭐ Item 1 is not a wording fix: the exit for Generated by Claude Code |
…ent census was false Contract review FAIL, four items plus one. The load-bearing one is a live behaviour regression, and it means the chosen exit was wrong rather than the wording. 1. The zero-producer census behind the retirement was FALSE. The producer does not write the key literally, it SPREADS it: `ListView`'s `case 'calendar':` flattens the authored block onto the node it emits, objectui's own published `ListViewSchema` accepts `calendar.dateField`, and `resolveTimelineDateBinding` documents it as the pre-#2231 alias for `startDateField`. A word-boundary text census is structurally blind to a key arriving through a spread. Reproduced by mounting the producer, capturing the emitted node (`titleField` + flat `dateField`, no `startDateField`) and rendering that exact node on both trees: merge-base draws, the retiring tree refused. ⇒ the exit is ROUTE TO THE PRODUCER, not retire. The rungs stay, both keys are ledgered by name, and the ledger asserts the producer still flattens so it reddens when `ListView` is fixed instead of rotting. 2. The cast count was contradicted by the checker. On the UNION seven of the fifteen reads were undeclared, not twelve: `ObjectGridSchema`'s index signature supplied the five `CalendarSchema`-only keys as `any`. Corrected on both the changeset and the source docblock, with the per-arm and per-union readings kept apart. 3. `ObjectCalendarBlockConfig` was unreachable — the same "measurably unreachable" property this card removed from the plugin-local `CalendarSchema`, one layer over. Now exported from `@object-ui/types`; verified by compiling a consumer import with a `@ts-expect-error` negative control, and by the same probe failing TS2305 at the merge-base. 4. "No conforming author could write them" was false nested: the element's `calendar` slot is `z.unknown().optional()`, not `CalendarConfigSchema`, so it refuses nothing. The grounds on both faces now say the spec settles the KEY and objectui settles the SHAPE. 6. The re-anchored control in the objectui#7313 pin could only come back green: a runaway paren match is shorter than the file and ends in a paren. Neither could the obvious repairs — a runaway closes on `useMemo`'s paren, stays balanced, and swallows only a dependency array, which is a legal argument. Re-anchored on the call's ARGUMENT LIST instead, so the arity assertion fires; proven by mutation. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
The dedup search for the producer-side card returned objectui#8355, which already asks exactly this question — "the renderer carries a lenient alias ladder that no published declaration spells: decide whether the aliases stay, are declared, or are refused" — open, unassigned, `pm:queue`. So no second card is opened; the routing note, the pin header and the changeset all point there instead. Two corrections for whoever takes it, both measured here: its key list says `dateField` / `startField`, and the ladder this renderer actually carries is `dateField` / `endField`; and its three options do not include the fourth this card takes — NORMALISE AT THE PRODUCER — which is the only one that refuses the alias without breaking a live authoring path. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Review response — FAIL at
|
| tree | calendar: { dateField, titleField } |
CONTROL startDateField |
CONTROL no binding |
|---|---|---|---|
merge-base e3cb47624e |
draws | draws | — |
40c825c7ff (retiring) |
⛔ "Calendar configuration required" | draws | — |
f964bb23a0 (repaired) |
draws | draws | refuses |
The emitted node is identical on all three — titleField plus a flat dateField, no startDateField — so the renderer is the only variable. The third control is new and load-bearing: it proves the refusal is still REACHABLE, so "draws" is a reading rather than a dead assertion.
⇒ the exit is ROUTE TO THE PRODUCER, the third exit the card offers and the one AGENTS.md #0.1 names once a producer exists. The rungs stay, behaviour is identical to the merge-base for both spellings, and both keys are ledgered by name in the pin with two assertions — each is STILL READ, and the producer STILL FLATTENS its block — so the ledger reddens when ListView is normalised instead of rotting.
⛔ No new card was opened. The dedup search returned objectui#8355, which already asks exactly this — "the renderer carries a lenient alias ladder that no published declaration spells: decide whether the aliases stay, are declared, or are refused" — open, unassigned, pm:queue. The measurement is posted there as evidence for its pending decision, with two corrections it needs: its key list says dateField / startField while the ladder measured here is dateField / endField, and its three options do not include the fourth this card takes — normalise at the producer — which is the only one that refuses the alias without breaking the authoring path.
② Cast count — corrected on both published artifacts
Union at the merge-base: DECLARED 8 of 15, NOT declared 7, nonsense control false. Seven needed a cast, not twelve. The five that compiled did so through ObjectGridSchema's index signature as any — which is the objectui#6914 defect itself, not an absence of one.
③ ObjectCalendarBlockConfig — now genuinely exported
Re-exported through objectql.ts with its from clause and named in the barrel. Measured by compiling a consumer import against the built dist: exit 0, with a @ts-expect-error on a nonsense name as the negative control (an unused directive would be TS2578); the identical probe against the merge-base exits 2 with TS2305.
④ "No conforming author could write them" — false nested, corrected
ComponentPropsMap['object-calendar'].calendar is not CalendarConfigSchema: wrapper chain ["optional","unknown"], different object reference. At that position a known-accepted key, a nonsense key, dateField, endField and even calendar: 42 all parse, while the props schema one level out still refuses a nonsense key — so the slot reading is not a dead parser. ⇒ the spec settles the key, objectui settles the shape. Both faces say so now, and the pin asserts it with those controls.
⑥ A control that could only come back green
Its stated control was call.length < src.length and call.endsWith(')') — both satisfied by a runaway match. useMemo's instead, so the slice still reports closed, is still balanced on every bracket kind, and swallows only a dependency array — a legal call argument. No structural test on the slice can separate them (179 chars genuine, 249 runaway).
Re-anchored on what the row actually claims: the arm is the call's last argument. Splitting the argument list at depth 0 gives two arguments for the real call and three for the runaway. Proven to fire — the mutation turns that row red with the ladder call no longer takes exactly (schema, arm): expected [ …(3) ] to have a length of 2 but got 3, restored by hash equality plus an empty git diff HEAD.
⭐ The same shape bit twice more in this repair: the producer-flatten ledger row first used a 2000-character window that reached the sibling case 'gallery':, so it is bounded at the next case label with that sibling asserted absent.
The gate list — derived, and the derivation is the deliverable
- 39 workflow files on
origin/main(ci.ymlas the enumeration-side positive control) - 29 reachable root
check:*scripts, resolving script chaining transitively - 67 distinct
node scripts/*.mjsinvocations — wherecheck-changeset-claims,check-vi-mock-override-shape,check-test-path-rootsandcheck-type-check-coveragelive
All run. 53 exit 0 standalone; 5 more exit 0 once the full closure is built (each printed its own "run the build first" / PREREQUISITE NOT MET rather than a verdict); check-node-esm-load exits 0 with --force-build; markdown-test-inputs --audit and check-governed-queue-guard --test exit 0 given the arguments their workflows give them, the latter reporting "NOT GOVERNED — 9 path(s) checked against 5 governed surface(s); none matched".
Five are CI-only by construction and refuse to guess, each naming what it lacks: check-merge-queue-head (GITHUB_REPOSITORY), check-required-check-set (HTTP 401), check-governed-queue-guard bare (GITHUB_EVENT_PATH), dependabot-merge-gate (HEAD_SHA), scripts/pm/check-half-states (token). NOT MEASURED, never green. One derivation artifact (a literal glob token) and one script absent from this branch (check-lockfile-dedupe.mjs) — checked rather than assumed: it is on current main and this branch's base predates it, so the branch is 6 commits behind, not the workflow broken.
Suites, lint and CI at f964bb23a0
vitest run packages/plugin-calendar/ packages/types/ scripts/ — exit 0, Test Files 379 passed | 2 skipped, Tests 9162 passed | 2 skipped, zero failures. vitest run packages/core/ packages/plugin-list/ packages/plugin-view/ apps/console/ — exit 0, 368 passed (368), 5681 passed (5681). Full build 43/43. tsc --noEmit and tsc -p tsconfig.test.json both 0.
Lint: population 4968 from eslint's own config resolution; 8 of the 9 changed files linted (the ninth is the changeset .md; the count derived from git diff --name-only); 0 new errors. packages/types/src/index.ts reports one error — the pre-existing no-restricted-imports on FormField, verified byte-identical to the merge-base's by diffing rule and message across both runs, shifted 1304 to 1305 by the added export.
CI: total_count 36 and 36 returned — 33 success, 3 skipped, zero non-green, all four shards.
⚠️ One process failure worth recording
The runaway-control ablation ran while ObjectCalendar.tsx had uncommitted edits, and its trap restored the file to HEAD, silently discarding them. AGENTS.md says to commit the fix before reverse verification; this is why. The edits were replayed from the session transcript and the repair was committed before any further trap-restore work. No work was lost, but the failure mode is exactly the one that rule exists to prevent.
Generated by Claude Code
Seat: round-2 report received; the landing question answered, the contract question declinedHead on record: Read at 2026-09-14T10:20Z. The one substantive reversal is the report, not a footnoteThe report's centre is that Q1 — should this PR close objectui#7311? A. Leave it as a reference.Answered, because this one is the seat's: it is a landing decision.
Same disposition for this PR against objectui#8651 itself: Q2 — the
|
Contract reviewReviewed head: Worked in a private detached worktree at that sha with its own ① The sweep — every claim the diff publishes or assertsPopulation bound, proved rather than asserted. The file set is exactly what
Claim count: 290 units reducing to 45 distinct propositions. 42 hold; 3 do not. The three that do not are items 1–3 below. What I measured myself, and what it returned. The producer really flattens — read, not taken on trust. The three-tree comparison is sound, and I made the soundness stronger than the claim. Rather than trusting three captures, I proved the producer is INVARIANT: A flat
The node is one constant and the producer is one blob, so the renderer is the only variable. The retirement was a live, user-visible regression and the reversal is correct. Both controls are non-degenerate on every tree. The controls are real — each ablated, each read at the stack frame rather than the summary line. Every ablation ran from the committed state under
The union readings, re-derived with the checker ( Accept-set, measured on the mirror. The export claim (round-1 item 3) is repaired. Enumerating the module's exports with the checker: Red-first is real. Copying the new pin onto a built merge-base, Scope, with non-vacuous controls. Of the 9 changed paths, 0 match any governed surface ( The 2000-char claim. Verified: the next ⛔ Three claims do NOT hold. All three are the same shape the round-1 record warned about — a state assertion repaired in one place while copies survive elsewhere. They are itemized below. ② Clause-2 carrier gate
The gate reads the comment set, so it is re-run after this comment lands; the second reading is reported in a follow-up note if it differs. ③ Every check green at the reviewed headRead from
Locally reachable gates, derived rather than asserted. My derivation and its counting rule: keys of root Suites, from the REPOSITORY ROOT with
Independence
Independence here is judged by independent measurement, not by any session trailer. I re-derived every reading in this record myself — the merge-base, the producer's calendar branch, the emitted node, the three-tree behaviour, the checker readings on both faces, the export enumeration, the accept-set, the gate exits, the CI tally — and inherited none of them from the PR body, the response comment, or the round-1 record. Where the implementer quoted a figure, I reproduced it rather than repeating it, and where a quoted reading was confounded (the unbuilt-tree NOT MEASUREDListed rather than silently omitted; none is rounded to green and none is rounded to red.
FAIL Defects — each self-contained and independently actionable
⛔ Nothing else in the 290 units failed. In particular the substantive reversal — routing Generated by Claude Code |
Seat: round-2 review adopted whole, FAIL relayed verbatim, repair dispatchedSeat: The review record above is adopted verbatim. ⛔ Not summarised, not reinterpreted, not partially taken — this seat has dropped items out of a reviewer's numbered list three times in this shift and the fix is verbatim relay with the PR record declared primary over any summary. All three items went to the implementer word for word.
What the seat wants on the record, because it is the half that is easy to lose in a FAIL⭐ The substantive reversal is CONFIRMED, independently. The reviewer did not take the three-tree comparison on trust — it proved ⭐ And the ledger is not decorative: ablating The three defects are one shape, and that is the thing to fixAll three are a state assertion repaired in one place while copies survive elsewhere — the shape that FAILed round 1. Item 2 is explicitly the third copy of a claim already corrected twice. ⇒ the repair order says plainly: ⛔ do not fix the three named lines and stop. Sweep for every surviving copy of each false assertion — word class, governing sentence, whole section, plus a cross-reference net — and report the sweep's reading with its counting rule. A three-line fix that leaves a fourth copy is another FAIL. Two readings from this review the seat is keeping
Generated by Claude Code |
…endar-union-reads
… claims
Round-2 contract review: three defects, all the same shape — a state assertion
repaired in one place while copies survived elsewhere. Swept rather than
line-fixed, and the sweep found two copies the review did not name.
COUNTING RULE for the sweep. Population: every line of prose this branch ADDS
(`git diff -U0 <merge-base>..<worktree>`, `^+` minus `+++`), all 9 changed
files, 927 lines. That is the right population and the whole-file one is not:
a false claim of mine can only live in text I wrote, and it is strictly wider
than the hunk-local read that missed round 2's item 1 (two copies ~840 lines
apart in one file are both in it). Because the population is small, each class
is enumerated EXHAUSTIVELY by keyword and hand-classified — no semantic matcher,
hence no matcher gap. A heuristic first pass MISSED a section header the review
named, which is why the exhaustive form replaced it.
1. The false union rule, both copies (ObjectCalendar.tsx, the pin). Replaced
with the reviewer's wording verbatim. Class A: 21 added lines contain
"union"; 2 stated the false rule; 0 remain.
2. The restated zero-producer census (ObjectCalendar.tsx) — both halves false,
and it also called the aliases "the retired spellings". Rewritten to say they
are ROUTED, still read, and have a producer. Class B: 36 added lines contain
"producer"; 1 asserted the retracted census in the indicative; 0 remain. The
surviving "No producer hands this component one" is about `ObjectGridSchema`
and is true.
3. Both stale section headers in the pin. Class C: 18 added lines contain
"retir*", every one hand-classified; 0 stale remain.
⭐ TWO COPIES THE REVIEW DID NOT NAME, found by the exhaustive pass: the pin
called the aliases "a retired spelling" twice more, in the comment-mask
rationale at two sites. Both reworded.
Also tightened, as the addendum invited: the mirror's "REFUSES NOTHING that
parses today", which is literally false for `calendar: 42`. Now split into the
KEY claim (true) and the VALUE narrowing (the point of the declaration).
⛔ No pre-existing line was edited. ObjectCalendar.tsx:200 ("lived under the
retired spelling", the objectui#7711 `filter.calendar` retirement) is absent
from the added-line population, asserted by a provenance control in the sweep.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Round-2 review response — three items adopted whole, swept rather than line-fixedAll three were the same shape: a state assertion repaired in one place while copies survived. So the repair is a sweep with a published counting rule, not three line edits — and the sweep found two copies the review did not name. The counting rule, published beside the readingPopulation. Every line of prose this branch ADDS: Method. Because the population is small, each class is enumerated exhaustively by keyword and hand-classified — no semantic matcher, hence no matcher gap. ⭐ That change was forced: a heuristic semantic sweep I wrote first missed the Controls. A provenance control asserts the reviewer's excluded line is outside the population; a liveness control asserts the enumerator still lists text it should. The reading
Every surviving occurrence in all three classes was hand-classified as legitimate — framed history ("An earlier cut of this card RETIRED them on a census that was FALSE"), a true statement about something else ( ⭐ The two copies the review did not nameClass C turned up two more, both in the pin's comment-mask rationale, both calling the aliases retired:
Both reworded to "a key that appears only in prose" / "a key named only in a comment" — accurate whatever the alias decision turns out to be, which is the property the original phrasing lacked. Item by item1 — the false union rule, both copies. Replaced with the reviewer's wording verbatim: a union member is available only when EVERY arm supplies it — by its own declaration OR through an applicable index signature. Each site now also carries the contradicting measurement locally, so the correction cannot drift from it again: five keys ( 2 — the restated zero-producer census. 3 — both stale section headers. Addendum item 3 — the borderline sentence: tightened, not left. ⛔ No pre-existing line was edited
|
Seat: round-3 repair received; review dispatched at
|
Contract reviewReviewed head: Merge-base derived here with Independence: own worktrees at the reviewed sha and at the merge-base under the scratchpad, own ① Sweep — population, claim count, every claim judgedPopulation bound, proved. File set from
Rule B was self-tested first on a constructed fixture with known legs (expected 3 + 2 = 5): it returned 5, where the naive Per-file: 13 changeset · 184 Partition of the 930: 559 prose · 314 code · 57 blank. Claim count: 270. 203 prose sentences (sentence splitter self-tested on 7 known legs first — plain, Is the added-line bound sound? Yes for the failure mode at issue, with one hole I then tested rather than assumed. A false claim authored by this branch can only live in text this branch added, and the bound is strictly wider than a hunk-local read — the two copies that survived round 2 sit ~840 lines apart and both fall inside it. The hole is claims this branch causes rather than authors: retyping The three round-2 items — repaired, and no surviving copy.
The two copies the review did not name (the pin's comment-mask rationale, at what are now lines 110 and 197) were real copies and are really gone — both appear as The copy the repair ADDED, and self-caught. Verified true of each file, which was the sharpest risk here. Provenance control — no pre-existing line was edited. The borderline item it tightened — true, and the controls hold. One probe, run on both trees, varying only the tree:
So "refuses no KEY that parses today" is true, "It does refuse VALUES" is true, and the Frozen shas. No 40-hex sha anywhere in the added population, and none of the round-1/round-2 heads appears. The body's one sha-like token, Substantive work re-verified, not regressed — by my own ablations from the committed state, each under
Routing verified independently: Gates reachable locally, derived from objectui#3378 guard. Every vitest run above was from the repository ROOT of my own worktree, so the invocation guard never fired. The rule for reading the console number: a verbose result line is ② Clause-2 carrier gateFrom Before this comment — exit 0: After this comment landed — exit 0, unchanged: same line, both carriers still agree. Run bare it exits 2 and says so itself — ③ Every check at the reviewed headRead from
PR is a draft; labels NOT MEASURED
RecordImplemented-by: the os-dev implementing agent for card objectui#8651 (a separate subagent run) FAIL
Generated by Claude Code |
…endar-union-reads
Seat: round-3 review adopted whole — one item, relayed verbatim; and a correction this seat owes on its own instructionsSeat: Three items → one. All three round-2 defects are repaired with no surviving copy, the two copies the review never named are really gone, no third unnamed copy exists, the copy the repair added and self-caught is verified true of each file, and the provenance control held — all 16 removed lines checked against the merge-base, none present there, so only text this branch wrote was deleted. The reviewer proved that control could fire rather than accepting its empty result. The substantive routing was re-verified by three fresh ablations with every control green. ⭐ The one item's last sentence is the deliverable, not the wording fix
The sweep was sound and cleared classes A ( ⛔ Not a re-litigation of round 2: round 2 measured the ⛔ A correction this seat owes, and it is worse than the defect it foundRound 3 measured something that falsifies an instruction this seat put in four dispatch orders today. The standing instruction was "run vitest from the repository root and confirm Independently verified by this seat at 12:16Z: ⇒
⇒ a false-green instrument, prescribed four times by this seat and found by a reviewer. The standing text is corrected; future orders name the project tag and require it self-tested on a leg where a real console file is collected.
|
|
…om the retraction set
Round-3 contract review: one item. The changeset published the very claim this
branch retracted, in a strictly weaker form — "The container keeps
`.passthrough()`, so nothing that parsed before is refused" — with no colon and
no noun KEY to scope it, and the same sentence disproves it eleven words later.
It ships to the CHANGELOG, so it was the most widely-read copy of all.
Verified rather than inherited, on this tree with five controls: `calendar: 42`
and `calendar: { startDateField: 42 }` are REFUSED, while a well-formed block,
`calendar.dateField`, `calendar.defaultView`, an unexamined key inside the block
and a nonsense top-level key all PARSE. So the refusal is value-level, not a
ceiling change, and the scoped sentence is the true one.
⭐ THE REAL FIX IS THE SWEEP'S CLASS DERIVATION, not the wording. Round 3's
classes came from the defects the REVIEW NAMED (union / producer / retir*).
That is why it honestly cleared those three and still missed this: the missed
claim shares no word with any of them. Classes must come from the RETRACTION
SET — every claim this branch changed the truth of. Nine classes now, R1..R9,
each with its predicate published beside its count and each proved able to fire
against the retracted sentence itself rather than against the corpus it judges
(a population control cannot fire once a claim is fully removed, and a class
that cannot fire reports VOID rather than a reading). Result: 0 unframed
surviving retracted assertions; the 5 signature hits are all quotations inside
their own retraction.
Also scoped, as the addendum invited: the changeset's other passthrough-leaning
sentence. "ListViewSchema accepts `calendar.dateField`" is true but weak —
measured here, that block also admits a nonsense key (while still refusing a
wrong value type), so the load-bearing half is the producer spread and
`resolveTimelineDateBinding`, not the accept. And the compound-noun ambiguity in
ObjectCalendar.tsx's rule statement is reworded; it still does not claim the file
measures the figure.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Round-3 review response — item adopted whole, and the sweep's class derivation fixedThe item is right and the wording fix is the small half. The real defect was how my sweep derived its classes, and that is what changed. Why round 3's sweep could not have caught thisIts classes came from the defects the review named — ⇒ classes must come from the RETRACTION SET: every claim this branch changed the truth of. Each retraction is a class; its instances live wherever that claim was written, in whatever vocabulary. Nine classes now. The class list, with its counting rulePOPULATION — every line of prose this branch ADDS:
Every one of the nine synthetic controls fires, and the nonsense control fires on none. Total unframed surviving retracted assertions: 0. The five signature hits are all quotations inside their own retraction ("What the false census said: zero producers…", "the cast count is NOT twelve", "…which is literally false for The item itself, measured on this tree before editing
So the refusal is value-level, not a ceiling change, and the scoped sentence is the true one. Rewritten to: "The container keeps The two addendum items — both taken, neither left silentThe changeset's other passthrough-leaning sentence: scoped. " The compound-noun ambiguity: reworded. "the checker reading this file records above" now reads "In the checker reading recorded above, five keys ride the union although only ONE arm declares them —
|
| rule | main run |
|---|---|
PROJECT TAG |@object-ui/console| |
0 |
path prefix apps/console/ |
0 |
raw token apps/console |
0 |
⭐ And the leg that proves the instrument can return non-zero — one real console file deliberately collected:
✓ |@object-ui/console| src/__tests__/App.docsPortalLazy.test.tsx > the /docs portal is genuinely lazy (objectui#5467)
PROJECT TAG rule : 1 path prefix : 0 raw token : 0
⇒ the reporter prints console paths project-relative, so the path-prefix and raw-token rules read 0 while console tests run. They are dead instruments; only the tag rule is a reading. My main run's 0 is therefore real.
⚠️ My own instrument failed first, and the control caught it
R5's positive control came back VOID on the first run: it was drawn from the population, and the claim was fully removed, so nothing could fire. A class that cannot fire returns the same value as a clean class — the failure mode this whole thread is about. Replaced with synthetic controls before any verdict was read from it.
Verification at the pushed head
origin/main merged first. vitest run packages/plugin-calendar/ plus the three touched packages/types pins — exit 0, Test Files 40 passed (40), Tests 345 passed (345). tsc --noEmit 0, tsc -p tsconfig.test.json 0. check:control-bytes 0, check:changeset-presence 0, check:changeset-no-major 0, check:changeset-claims 0. CI from commits/{sha}/check-runs: total_count 36 and 36 returned — 33 success, 3 skipped, zero non-green.
PR stays draft; no label hung or cleared; not enqueued.
Generated by Claude Code
Contract reviewHead reviewed: FAIL The round-3 item is genuinely repaired, the changeset's headline measurement reproduces exactly, and every gate is green. It fails on the thing this round was asked to prove: the sweep's own method claim. Nine classes do not cover the retraction set, and I measured four surviving retracted assertions — one of them in published user-facing docs. ① Sweep — every claim the diff publishesClaim count: 181. Population bound: 930 added lines — PROVED.
A second corpus, because the first one cannot hold the answer. The ⭐ instruction was that the classes must cover every claim the repair CHANGES the truth of. A claim the branch falsifies can survive on a line the branch never touches, so for the retraction sweep the population is the whole tracked tree at the reviewed head ( The headline measurement reproduces, independently. Census over the merge-base renderer with comments masked: 15 keys read (controls: sees
Control on that instrument: the three keys declared on both arms ( The round-3 item is repaired. Measured on the built mirror, not asserted — Also verified true: the container's member list is exactly 5 ( Where it fails: the retraction set has 18 members, not 9. Re-derived independently — every claim this branch changes the truth of: Intra-branch retractions (an earlier revision of this PR asserted it and withdrew it): no producer · aliases retired · twelve casts · "no conforming author could write them" · "no casts remain" · the every-arm rule · Tree-state retractions (the merge-base tree asserted it and the landing falsifies it): the props type is Of the nine classes shipped, eight are intra-branch; exactly one (R4, the ② Carrier gateExit code captured before any pipe, both times, run from
③ Every check greenRead from
Locally, from the repo root with
Items1. The Measured at this head, with the checker and not a grep: It is a claim born false: true at the merge-base — the diff's own CI cannot catch it, which is why all 36 checks are green: Fix. Rewrite the marker's reason to the head's truth — the block is a fragment because the literal lacks the required 2. The sweep's method claim — "Total unframed surviving retracted assertions: 0" — is false, and by its own construction could not have been otherwise. Item 1 is one counter-example. Three more, all on untouched lines, all present-tense assertions that
(I am not scoring the two Two separable causes, each measured:
Fix. Draw the retraction population from the whole tracked tree at head, not from added lines; and derive each class's predicate from the retracted claim's referents — the symbol names 3. Enclosing declaration resolved with the checker rather than inferred from proximity: the line is owned by At this head Fix. Cite the ladder and the arm without the first argument — e.g. "read FIRST by the shared record-source ladder in 4. The alias-routing justification is non-discriminating, and "by name" is false. Published twice, and its test control shares the suspect part. Added-line sites: Measured on the installed So the stated ground does not distinguish the two routed keys from the five the same PR declares; applied as written it would require undeclaring the canonical twins this renderer reads. The PR knows the asymmetry runs the other way — And the control cannot fire. The test's control for that row is (The same wording ships inside a Fix. State the real ground — these are deprecated pre-#2231 aliases being routed to the producer under carrier objectui#8355, which is why they are not declared — and replace "refuses both by name" with the measured mechanism ("the spec's NOT MEASUREDClassified, never scored as a pass or a red.
Disclosed errors and near-misses of my own
Hard-rule observationsCreated no GitHub review — this is a plain issue comment. No approve, merge, enqueue; draft left as Implemented-by: os-sam Generated by Claude Code |
…endar-union-reads
…t not vocabulary Round-4 contract review: four items, adopted whole. Verified against the review comment itself (5664609021) rather than the relayed copy. THE FINDING BEHIND ALL FOUR. The retraction set has two halves that live in different places. INTRA-BRANCH retractions (an earlier revision of this PR asserted it and withdrew it) live in ADDED lines. TREE-STATE retractions (the tree asserts it and this landing falsifies it) live in UNTOUCHED lines. Eight of round-4's nine classes were intra-branch and the population was added lines, so the population bound and the class list were the same blind spot counted twice and could not correct each other. TWO RULES REPLACE THEM, and they are the deliverable: RULE 1 POPULATION — draw it from the WHOLE TRACKED TREE AT HEAD, never from the diff. A claim this landing falsifies was written by someone who never read the retraction, so nothing about it correlates with what I touched. RULE 2 PREDICATE — derive it from the retracted claim's REFERENTS (the symbol names) never from the vocabulary of the retracting sentence. The surviving text says `ObjectGridSchema | CalendarSchema` and never says "union". Run that way over 7521 tracked files / 1.68M lines, three referent classes found every site the review named PLUS one it did not: the same stale call-spelling in `zod/objectql.zod.ts`. Each class carries a CAN-FIRE leg against a synthetic string, never against the corpus. 1. `content/docs/plugins/plugin-calendar.mdx` — the doc-snippet marker asserted in the present tense that the prop is the grid/date-picker union and that neither arm admits an `object-calendar` node. Measured here: the shipped type is `ObjectCalendarSchema`, and compiling that exact block gives TS2741 x1 / TS2322 x0. Reason rewritten to the discriminant, objectui#7311 framing dropped. ⛔ The old measurement is NOT asserted to have held when written — that needs a build of the old tree, which was not done. 2. The method above, plus the one instance it surfaced that publishes: `.changeset/7632-*.md` asserted the union in the present tense. Past-tensed. The two `packages/core` twins are outside this card's file surface and are already carried by objectui#9473. 3. The stale `resolveRecordSourceConfig(schema, 'array')` citation, both sites — `objectql.ts` and the one in `objectql.zod.ts` the review did not name. Now cite the ladder and the ARM, never the call shape. 4. "the spec refuses both by name" is false. Measured on 17.4.0: the props object is strict with nine flat members, and it answers the two aliases, a nonsense key AND the five canonical field keys with the identical `unrecognized_keys` diagnostic — so the ground did not discriminate, and applied as written would have required undeclaring the canonical five. Replaced with the measured mechanism and the real ground (deprecated pre-#2231 aliases, carrier objectui#8355). Same correction applied to all three `allDayField` copies of the claim. And the test's control moves from a nonsense key to `startDateField`: a nonsense key is refused for the same reason the subject is, so it shared the suspect part and could not separate a targeted refusal from blanket strictness.⚠️ Three claims of my OWN were caught by re-reading this repair diff before pushing, none of which CI could see: "that was true when written" (not measured), "the five canonical field keys this same card declares" (this card declares `calendar`, not those five), and an over-broad "the reasoning below is unchanged". Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Round-4 review response — four items adopted whole. Head
|
| half | what it is | where it lives |
|---|---|---|
| intra-branch | an earlier revision of this PR asserted it and withdrew it | added lines |
| tree-state | the tree already asserts it and this landing falsifies it | untouched lines |
Round 3 drew classes from the defects the review named. Round 4 drew them from the claims this branch retracted — the right direction, and still blind, because both kept the population at added lines and both wrote predicates in the vocabulary of the sentence that did the retracting. Eight of round 4's nine classes were intra-branch. A population of added lines and a class list of intra-branch retractions are the same blind spot counted twice, so they cannot correct each other.
The two rules that replace them
RULE 1 — POPULATION. Draw it from the whole tracked tree at head, never from the diff. A claim your landing falsifies was written by someone who never read the retraction, so nothing about it correlates with what you touched.
RULE 2 — PREDICATE. Derive it from the retracted claim's referents — the symbol names it is about — never from the vocabulary of the sentence that retracted it. The surviving text says
ObjectGridSchema | CalendarSchema; it never says "union". A prose predicate cannot reach it; a symbol predicate reaches all of them.
Counting rule. Population = git ls-files at head, text extensions, dist/ excluded — 7521 files, 1 678 341 lines. Unit = one line. Each class carries a CAN-FIRE leg against a synthetic string — ⛔ never against the corpus, which cannot fire once a claim is removed — and a must-not-fire leg.
| class | predicate | hits | verdict |
|---|---|---|---|
| T1 the parameter is the union | ObjectGridSchema\s*|\s*CalendarSchema (either order) |
5 | 3 correct (two past-tense changesets, one card-history line); 2 are the packages/core pair, outside this card's file surface and already carried by objectui#9473 |
T2 the ladder is called (schema, ARM) |
resolveRecordSourceConfig\s*\(\s*schema\s*, |
8 | all legitimate — 'view-data' for other blocks, plus one quoting the retired literal inside its own explanation |
T3 neither arm admits an object-calendar node |
admits\s+an\s+.?object-calendar |
0 | cleared |
Run this way, the three referent classes found every site the review named, plus one it did not: the same stale call-spelling in packages/types/src/zod/objectql.zod.ts. That is the rule earning its keep on the first run.
The four repairs, as they now read
1 — content/docs/plugins/plugin-calendar.mdx (the published one). The marker asserted in the present tense that the prop is declared the grid/date-picker union and that neither arm admits an object-calendar node, recording TS2322 x1. Measured here, not inherited: the shipped type is schema: ObjectCalendarSchema (plugin-calendar/dist/ObjectCalendar.d.ts:66), and compiling that exact fenced literal against the built workspace gives TS2741 ×1 (Property 'type' is missing) and TS2322 ×0. The reason now says the block is a fragment because the literal omits the required type: 'object-calendar' discriminant; the objectui#7311 "stale declaration" framing is gone.
⛔ One thing deliberately not asserted: that the old TS2322 ×1 held when it was written. That needs a build of the merge-base, which was not done — the review lists the same refusal. My first draft of this repair claimed it; re-reading my own diff caught it.
2 — the method above, plus the one instance it surfaced that publishes: .changeset/7632-shared-record-source-config.md:35 asserted the union in the present tense. Past-tensed, with the scope of what still holds narrowed to the conclusion I could actually check. The two packages/core twins are outside this card's declared file surface and are already carried by objectui#9473, which names them as its second half.
3 — the stale call-spelling citation, both sites. packages/types/src/objectql.ts and packages/types/src/zod/objectql.zod.ts (the second unnamed by the review, found by the sweep). Both now cite the ladder and the arm, never the first argument, so the citation cannot re-stale on the next call-shape change.
4 — "the spec refuses both by name" was false, and the ground did not discriminate. Measured on installed @objectstack/spec 17.4.0: ComponentPropsMap['object-calendar'] is strict with exactly nine flat members (calendar data defaultView filter loading locale objectName sort staticData) and answers with the identical unrecognized_keys diagnostic for the two routed aliases, a nonsense key, and the five canonical field keys ObjectCalendarSchema declares. So the refusal is blanket strictness, it says nothing about these two in particular, and applied as a reason it would have required undeclaring the canonical five. Replaced with the measured mechanism and the real ground — deprecated pre-#2231 aliases whose question has an open carrier, objectui#8355, so declaring one would settle that card by accretion.
allDayField description (one added by this diff, two pre-existing in the same in-surface file). All three corrected — leaving a known-false twin of a claim I was fixing is the exact pattern this series keeps finding. CalendarConfigSchema refuses allDayField and zzqxNoSuchKey identically; it is a strict four-key object, not a by-name refusal.
And the control, which was the non-cosmetic half. The row's control was a nonsense key — false for exactly the same reason the subject is false, so it shared the suspect part of the instrument and could not separate "the spec singles these two out" from "the spec refuses every undeclared flat key". It is now startDateField: a key this schema declares, which the spec also refuses flat. Its false is the finding rather than a failure.
Readings at this head
Test figure, reconciled with its predicate. packages/types pins"; only two are touched (object-calendar-record-source-7313, zod-mirror-parity) — calendar-flat-color-allday-8466 was read, never edited. Both selections, with the exact commands:
| selection | command | result |
|---|---|---|
| A — what I actually ran | vitest run packages/plugin-calendar/ …7313.test.ts …8466.test.ts …zod-mirror-parity.test.ts |
exit 0, 40 files, 345 tests |
| B — the touched predicate | same minus …8466.test.ts |
exit 0, 39 files, 326 tests |
345 is correct for A and reproduces. ⛔ I cannot reproduce the reviewer's 337 under either rule and do not adopt it; the discrepancy is in the selection, and A's command above is the one that produces mine.
apps/console, by the project-tag rule (the path-prefix and raw-token rules are dead instruments — the reporter prints console paths project-relative): |@object-ui/console| = 0 on the run above, with the tag field populated.
Gates. tsc --noEmit 0 · tsc -p tsconfig.test.json 0 · full build 43/43 · check:doc-snippet-types 0 · check:doc-example-types 0 · check:doc-links 0 · check:doc-fence-languages 0 · check:control-bytes 0 · check:changeset-presence 0 · check:changeset-no-major 0 · check:changeset-claims 0 · check:new-cross-file-line-citations 0. doc-snippet-types and doc-example-types first exited 2 against an unbuilt closure and said so themselves — NOT MEASURED, never a red; both 0 after the full build.
② carrier gate — PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9471 from /home/user/objectstack: exit 0, "PR #9471 / card #8651 — the clause-② declaration is readable in the fixed spelling and both carriers agree."
CI at 7a21da6933f0, from commits/{sha}/check-runs: total_count 36, 36 returned — 33 success, 3 skipped, zero non-green.
Mergeability. mergeable_state: behind. Reading it myself at this head it returns clean with mergeable: true — origin/main was merged in during this round. State moves, so both readings are recorded; a landing seat should re-read rather than trust either. ⛔ I did not enqueue, approve, merge, flip draft, or touch a label.
calendar, not those five), and an over-broad "the reasoning below is unchanged". The adversarial re-read of one's own repair is the only instrument that catches the copy introduced by the repair.
Implemented-by: claude/issue-8651-calendar-union-reads
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
Generated by Claude Code
Contract reviewHead reviewed: PASS All four round-4 items are repaired, and I re-measured each with my own instrument rather than reading the response record. One residual is named below; it is not scored, and my reasons are given so a later seat can disagree with the reasoning rather than guess at it. ① Sweep — every claim the diff publishesClaim count: 192. Population bound: 992 added lines — PROVED. Retraction population: 7764 tracked files — PROVED.
Counters self-tested on a firing positive AND a non-firing negative before any of them was read. Occurrence counter: fires 3 on a synthetic carrying two matches on one line, 0 on a negative, 0 on a nonsense predicate — and on that same fixture ⭐ The population is the whole tracked tree, not the diff — the exact correction round 4 demanded. I also re-ran this lane's pathspec trap at this head and it reproduces: The four repairs, each re-measured1 — The marker now says the block is a fragment because the
The shipped declaration the marker cites is exact: The marker also declines to assert that the old 2 — the method claim, and the one site that publishes. REPAIRED.
Re-derived the retraction population myself, from referents, over all 7764 files:
Occurrence totals cross-checked with The two survivors are
3 — the stale call-spelling citation. REPAIRED at both sites, including one round 4 never named.
⭐ Ownership resolved by brace-walk, not proximity — this card's own subject is a type-ownership question, so the review owes the same discipline. A masked-source walker (strings and comments blanked, depth counted) puts the repaired citation inside 4 — "the spec refuses both by name". REPAIRED, and every measured claim in the replacement reproduces.
The control, which was the non-cosmetic half, is genuinely fixed. All three copies of the What the round-5 repair could NOT have broken, and how I knowThe repair commit touches six files. In the three that could move a structural figure, ⭐ The merge-base derivation earned its keep here: the round-4→round-5 range contains a merge of ② Carrier gateExit code captured as
⛔ The C4 independence limb printed no row, and I draw no conclusion from that in either direction. ③ Every check greenRead from
Tests, run by me in my own worktree. My selection — ⭐ Round 4's unreconciled 337-vs-345 gap closes, and it closes against round 4. I reproduced the response record's selection A verbatim: 40 files, 345 tests,
Ambient defect, seen green. Named residual — measured, NOT scored
The test is named "⛔ neither is DECLARED — declaring would accept what the platform refuses", while the comment inside it now reads "⛔ Blanket strictness cannot be the reason these two stay undeclared." That is a real tension inside one file, and it is the same shape this series keeps finding — a true-but-retracted framing surviving where the repair did not look. My own referent predicate found it; the response record's T1/T2/T3 could not have, since the name carries neither the union symbols nor "by name". I am not scoring it, for three measured reasons: (a) the sentence is literally true — I measured the spec refusing both aliases flat; what round 4 refuted was its use as a discriminating ground, not its truth; (b) NOT MEASUREDClassified, never scored as a pass or a red.
Disclosed errors and near-misses of my own
Hard-rule observationsCreated no GitHub review — this is a plain issue comment; Shared checkouts proved untouched before and after:
Implemented-by: claude/issue-8651-calendar-union-reads Generated by Claude Code |
② after — the value I could not write before the comment existed
Verdict stands: PASS at Implemented-by: claude/issue-8651-calendar-union-reads Generated by Claude Code |
Carrier discharged —
|
| verdict | round 5, 5666038388, 2026-09-14T15:01:19Z (② after-value in its follow-up 5666049957) |
| head it declares reviewed | 7a21da6933f0c81b4de94c69b1d225974db360c0 |
| PR head at discharge (15:03:53Z) | 7a21da6933f0c81b4de94c69b1d225974db360c0 |
| comparison | ⭐ character-identical, compared as strings — ⛔ not by prefix, ⛔ not by short sha |
| verdict token | **PASS** present |
⭐ This is what round 4 could not give. Round 4's verdict sits at 6fd5711526d4, and the four repairs pushed past it — so precondition ① was genuinely unsatisfied until round 5 landed. ⛔ The carrier was correctly NOT dischargeable before now.
⭐ Round 5 also repairs round 4's authorship spelling, which wrote Implemented-by: os-sam (an account login) and Reviewed-by: claude-code (model prose) — both the wrong kind of token. The pair now reads, per check-clause2-carriers.mjs:2234-2244, with the branch as Implemented-by: because the dev was mode:subagent and has no session of its own:
Implemented-by: claude/issue-8651-calendar-union-reads
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
The other two gates, at this same head
- ②
--pair 9471from/home/user/objectstack, exit captured before any pipe, run after the verdict landed: exit 0. ⛔ Silent on the clause-② independence limb — and the mechanism is now established rather than open:check-clause2-carriers.mjs:2127-2132declares that a verdict on a PENDING pair's PR thread is invisible to C4, becausepair.prCommentsis filled only for COMPLETED pairs. Every verdict in this series lives on the PR thread. ⇒ C4 was never going to fire. Independence rests on the maintainer's ruling 「新起的子代理算干净」, judged by independent measurement — and round 5 qualifies on its own evidence: its own worktree with its ownpnpm install, a 7,764-file population proved by three agreeing enumerations, a 992-added-line count proved by three agreeing counters, and three disclosed instrument failures of its own, including two compile probes whose zeros wereTS2307/TS5101rather than readings. - ③
commits/7a21da6933f0…/check-runs?per_page=100:total_count36 == 36 returned, 33success+ 3skipped, 0 not-green, 0 still running; legacy combined statussuccess;mergeable: true,clean.⚠️ Expected to become 37 at undraft —Governed Surface Queue Guardstarts on thepull_requestevent — and the PR will not be enqueued until that one reports. - Governed surface —
pulls/9471/filesre-read at this head: 11 paths, zero underdocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md.
Carried forward, ⛔ not fixed here
Round 5 measured two packages/core referent survivors that are genuinely false at this head, dropped them as outside this diff's surface, and found a third site nobody had enumerated (record-source-config.behaviourNeutrality-7632.test.ts:312). All three belong to objectui#9473, which round 5 fetched and confirmed open. ⇒ its fixer should sweep by predicate, ⛔ not by the two recorded line numbers.
Also carried: calendarUnionReads-8651.test.tsx:387's test name, measured and deliberately not scored — the sentence is literally true and the corrective sits three lines below it in the same block. A rename would still be an improvement.
Generated by Claude Code
Part of objectui#8651.
⛔ Not the whole card. The
navigationarm is carved out of this diff and stays open: objectui#8652 carries that family, the maintainer ruled B there (declare it on the platform element schemas first, then mirror), and the spec half is objectstack#17987. So this PR is deliberatelyPart of, never a closing keyword — the card must survive to receive that arm.needs:contract-reviewbelongs on this PR: the claim declaredClause-②: yes.The card's premises, re-measured before editing
The card measured at
origin/main154fe2a; this branch was cut well after that and has since been merged forward, so ⛔ take the merge-base fromgit merge-baserather than from this sentence or from the PR'sbase.sha. Every claim was re-derived with the TypeScript checker (checker.getPropertyOfType, never a grep — objectui#8410), on the plugin's own program.allDayField·calendar·endDateField·startDateField—noonObjectGridSchemaandnoonCalendarSchemacolorFielddateFielddefaultViewendFieldtitleFieldonCalendarSchemaonly;datanavigationstaticDataonObjectGridSchemaonlycalendar157 to 210,startDateField162 to 215,endField165 to 218,titleField166 to 219,colorField167 to 220,allDayField168 to 221,defaultView231 to 284,data274 to 340,staticData275 to 341,navigation645 to 803CalendarSchemais atObjectCalendar.tsx:74false;ObjectGridSchematrue,CalendarSchemafalsefilter,objectNameandsortwere left out because they are declared on both arms — correct for the card's question, but it means the card never asked what the props type SHOULD be⭐ And the premise the card could not have had.
ObjectCalendarSchema— this repo's published interface for theobject-calendarelement, and its zod mirror — already declares 11 of the 15 keys this renderer reads,allDayField,startDateField,endDateField,titleField,colorField,defaultView,dataandstaticDataamong them (objectui#8466, #8174, #8314, #9239). Neither arm of the union the props were annotated with is that interface. The reads were never undeclared for want of a declaration.ObjectGridSchemacarriesBaseSchema's index signature, so the fiveCalendarSchema-only keys resolved through it and compiled with no cast, silently typedany. Per-arm (what the card tabled) and per-union (what the compiler acts on) are different numbers; both are measured, and the union reading is the worse one for the old shape, not the better one.The union is the answer to the card's first question
The card asks first whether
ObjectCalendaris supposed to accept both shapes. Measured: no, and neither arm survives.ObjectGridSchemaistype: 'object-grid'. Nothing hands this component one. The single production call site is this package's ownindex.tsx, and both of its registrations —object-calendarandcalendar— publishOBJECT_CALENDAR_INPUTS, theobject-calendarsurface.CalendarSchemawas declared locally inObjectCalendar.tsx, absent from this package's barrel (the packageexportspublishes only.), so no importer could ever name it — and it SHADOWED@object-ui/types' own publishedCalendarSchema, the date-picker primitive reachable atui:calendaronly (objectui#8499). Two layers, one word.The shape this leaves is the family's:
ObjectKanbantakesObjectKanbanSchema,ObjectGantttakesObjectGanttSchema,ObjectMaptakesObjectMapSchema— andplugin-mapis registered under two tags (object-mapandmap) with a single published props type, exactly as this package is.The exit taken, per key AND per union arm
allDayFieldObjectCalendarSchemastartDateFieldendDateFieldcalendarinputspublishes it,getCalendarConfigreads it FIRSTtitleFieldCalendarSchemaonlycolorFieldCalendarSchemaonlydefaultViewCalendarSchemaonlydataObjectGridSchemaonlystaticDataObjectGridSchemaonlyobjectNamefiltersortdateFieldCalendarSchemaonlyendFieldCalendarSchemaonlynavigationObjectGridSchemaonly⭐⚠️ Earlier revisions justified this with "a union member is declared only when EVERY arm declares it" — that rule is wrong for a union whose arm carries an index signature, which is how five other keys resolved here, and it survived in two source files after the body disavowed it. The correct rule, now in both: a union member is available only when EVERY arm supplies it — by its own declaration OR through an applicable index signature.
navigation's verdict is INVARIANT under this change, which is how the carve-out is honoured rather than merely declared.ObjectGridSchemadeclaresnavigation;CalendarSchemaneither declares it nor has an index signature to supply it; so the union does not carry it — and neither doesObjectCalendarSchema. Same verdict before and after, the read untouched, the cast left standing on purpose. Ledgered BY NAME in the pin with an assertion that it is STILL READ, so a stale exception reddens.Accept-set changes, one by one
ObjectCalendarSchema.calendar, TS face — was admitted byBaseSchema's[key: string]: anyasany; now typed. Narrows only:calendar: 42no longer type-checks.ObjectCalendarSchema.calendar, zod mirror — was admitted byBaseSchema's.passthrough()unexamined; nowObjectCalendarBlockConfigSchema.optional(). It refuses no key that parsed before — the container keeps.passthrough(), so a block carryingdefaultView,dateFieldor anything else still parses — and it does refuse values, which is the point of declaring:calendar: 42andcalendar: { startDateField: 42 }are refused where both were admitted unexamined.ComponentPropsMap['object-calendar'].calendarisz.unknown().optional(), notCalendarConfigSchema, so at that position the protocol accepts everything. The spec settles the KEY; objectui settles the SHAPE — the fourCalendarConfigSchemanames plus objectui'sallDayField, which is exactly what the renderer destructures.getCalendarConfig, runtime — NO CHANGE. An earlier revision retired thedateField/endFieldalias rungs and that was a live regression; they are kept, so both spellings behave exactly as they do at the merge-base.Public-surface changes, one by one
ObjectCalendarComponentProps.schema:ObjectGridSchema | CalendarSchemabecomesObjectCalendarSchema.object-gridnode, or an object literal withtype: 'calendar', to theObjectCalendarelement. Neither is a node this renderer is registered for. Declaredminorper the repo's version policy (a fixed-groupmajoris mechanically unavailable), with the breaking semantics spelled out in the changeset.ObjectCalendarProps, the deprecated alias of that type, follows it — it denotes the same type.CalendarSchemais deleted. Not a published-surface change: this package'sexportspublishes only., and the barrel never re-exported it. It is measurably unreachable, which is also why it could shadow the published name for so long.ObjectCalendarSchemagainscalendaron both published faces, and@object-ui/typesexports the inferred typeObjectCalendarBlockConfig.CalendarSchema, one layer over. Measured by COMPILING a consumer import: a probe importing it from@object-ui/typesagainst the builtdistexits 0 with a@ts-expect-erroron a nonsense name as the negative control (an unused directive would be TS2578), and the identical probe against the merge-base'sdistexits 2 withTS2305: Module '@object-ui/types' has no exported member 'ObjectCalendarBlockConfig'.Verification
Red-first. The pin was written and run against the unmodified tree first:
vitestexit 1 with five failing rows, andtsc -p tsconfig.test.jsonexit 2 withTS2344: Type 'false' does not satisfy the constraint 'true'plus fourTS2322/TS2339on the union — the type-level rows are red only undertsc, because vitest strips types.⭐ The central row re-derives its own population every run, per AGENTS.md #9 — no count and no key list is written down for it. The keys READ come from a cast-aware census over
ObjectCalendar.tsx(objectui#6576'sschemaReads, comments masked first); the keys DECLARED come from the zod mirror's own.shape, never from parse acceptance, which under.passthrough()cannot tell "declared" from "admitted unexamined". Both halves carry a control asserted to hold the opposite verdict.Two pins re-anchored, and why that is the objectui#8832 rule rather than a workaround
types/src/__tests__/object-calendar-record-source-7313.test.tspinned the literal stringresolveRecordSourceConfig(schema, 'array')— an anchor on how the FIRST ARGUMENT is written. It now reads the arm out of the call's own ARGUMENT LIST over a comment-MASKED source.types/src/__tests__/zod-mirror-parity.test.ts—SPEC_DERIVED_PAIRSgainedobjectql.zod.ts#ObjectCalendarSchema: the mirror now composes a spec-derived sub-schema, so a spec bump moves one side of the pair.resolveRecordSourceConfigin@object-ui/coredeclares its parameter'sdataasViewData, while its owndataArmcontract — and itsauthoredDataIsOnTheDeclaredArmpredicate, which takesunknown— admit an ARRAY on the'array'arm, which is the armobject-calendardeclares. The retired union hid it:ObjectGridSchema.dataisViewData, so the call type-checked while saying something this block does not mean.packages/core/is outside this card's file surface, so the call site instead passes the three members the resolver documents itself as reading, one by one. Filed as objectui#9473.Scope
⛔ Untouched:
navigation,content/docs/releases/, theframeworkchunk ceiling, objectui#8225, and every governed surface (docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md) — the last verified mechanically, not asserted:check-governed-queue-guard --testover the changed paths reports "NOT GOVERNED — 9 path(s) checked against 5 governed surface(s); none matched". No test was skipped, disabled or quarantined; no label was added or removed; this PR stays draft.The objectui#6273 collision ratchet, and the reason it fired
An early head reddened
Test (shard 4/4): the baseline listed aCalendarSchemacollision this PR's third public-surface change resolves, and the ratchet refuses to let a resolved entry sit there. Taking the entry down NARROWS the exemption set; nothing was added, loosened, skipped or disabled.Gate and suite readings
node scripts/*.mjsinvocations, which is where roughly twenty further reachable gates live. Every one of them was run; the per-round records carry the readings and the NOT-MEASURED classifications.CI is read from
commits/{sha}/check-runsat the head of the moment — the authority, and ⛔ not the PR'sbase.sha, which is the base BRANCH TIP rather than the merge-base (git merge-basegives that). The reading is reported per round in the response comments rather than frozen here, andtotal_countis compared with the returned length every time so a truncated page cannot read as a clean list.Session reference, in prose because a body edit strips it from the footer:
session_01L5xpA5q533BgTTNADibEFt.Generated by Claude Code