fix(spec): the calendar binding warning describes the refusal objectui#7029 left, not the fallback it deleted - #19623
Conversation
…leted literal fallback `VIEW_BINDING_BLOCKS`' calendar row and the `view/layout-without-binding` body both asserted a renderer fallback to literal `start_date` / `end_date`. Re-measured on objectui `main` at `0cf2d6644`: `ListView.tsx`'s calendar branch restates only declared bindings, and `ObjectCalendar`'s `getCalendarConfig` resolves `null` into the "Calendar configuration required" refusal screen. The warning body is now per type, keeps its prescription, and names `calendar.startDateField`; no severity moves. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
…ctui pin too The two reads behind the corrected row are byte-identical at `.objectui-sha` = `87af769e9`, so the row describes the console this repo ships and not only objectui's head. The asserting spelling wires the dependency edge `check:objectui-pin-citations` enforces: the next pin bump fails loudly on this record instead of letting it rot. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c648b65f0518786fcaee2dfa30356ebf21513bfe && git checkout c648b65f0518786fcaee2dfa30356ebf21513bfe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 4530d21191dc283a5badc9012cba3951aeff1cc5 && git checkout -B drift-repro 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 && git merge --no-ff 4530d21191dc283a5badc9012cba3951aeff1cc5
node scripts/docs-audit/affected-docs.mjs --json 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3
|
Contract reviewServed-tier: 160/160
① Derived judgments1. Cross-repo re-measurement — CONFIRMED on the findings, ⛔ OVERTURNED on the stated method. Measured at both refs. Half ①, 2. The two deltas — both CONFIRMED, both confirm rather than reverse the card. objectui#8170 is cited verbatim in 3. The prescription survives, and the five other messages are untouched — CONFIRMED by execution, not by reading. The module has no imports, so 4. Severity — the line-drawing is right, the reason given for it is ⛔ FALSE. Not moving severity here is correct. But the PR justifies it by deferring to a pending ruling on #16577, and #16577 was ruled and closed 5. Per-row verdicts — ALL SEVEN re-derived at the pin, not a spot-check; all seven match. 6. Scope fence — CLEAN. 3 files, +177/−17, all inside 7. Semver — correct, and what the green is not evidence for. 8. CI at this head, by job conclusion, latest run per check NAME. 35 check runs, all 9. NOT-MEASURED handling — CORRECT. Exit 3 is a declared convention, not a dev invention: 10. Self-test-only families — one citation is the alias. 11. ⭐ The durability claim — VERIFIED, with a lit ablation. 12. The PR body as a carrier — ⛔ two false sentences. See both fail bases. Everything else in it was checked and found true of the tree at head, including the acceptance notes: no test anywhere pinned the old warning string (repo-wide grep for the generic sentence returns 8 hits — 2 in this PR's own test file, 6 in released 13. The drift-check disposition — SOUND, and the zero is a reading. All 9 hand-written rows carry the same anchor kind: ② Semver level
③ Boundary flags⛔ FAIL BASIS 1 — the deferral to #16577 is stale, and inverted, in three carriers inside the diff plus the PR body. ⛔ FAIL BASIS 2 — the PR body's "byte-identical on both halves at both refs" is false on half ②. Correctable, ⛔ not fail bases:
Everything else re-derived above is carried as correct. The engineering is good: the per-type split is genuinely an override rather than a rewrite and is proved so by execution, the fence on the three stale rows is the right call and is properly filed, and the asserting pin citation makes this record the first one in the table that cannot silently rot. The two fail bases are both prose carriers, both cheap to fix, and fixing the first makes the PR's argument stronger than it currently is. Implemented-by: VERDICT: FAIL Generated by Claude Code |
…iers The three in-tree carriers this PR added deferred the calendar route's severity to #16577 as an open question. It is not open: #16577 ruled B on 2026-09-11 (comment 5634033966, card closed completed) and fixed the route at warning-class under ADR-0078 §1 -- reasoning from exactly the premise this PR re-measures, that both doors are loud (the os validate warning, and ObjectCalendar's reachable refusal screen after objectui deleted the start_date / end_date floors). So the corrected row is not a change whose severity consequence is deferred; it is the evidence the standing ruling already rests on. The changeset ships into packages/spec/CHANGELOG.md, which is where the previous stale claim survived, so a second stale claim there would reproduce the defect this PR exists to correct. Also adds one clause to the calendar row: the quoted refusal literal is the text rendered AT THE PIN. objectui#10101 landed after the pin and moved it into a tt('calendar.configRequired', ...) default, so a non-English locale on objectui's head renders other words. Harmless for the console this repo ships, and the asserting pin citation below it is what reds at the next bump. Prose only: no schema, no severity, no behaviour, no new assertion. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
Seat note on the fix round — two of the review's items were WRONG, and one instruction in my own brief wasHead moved ① The PR body was corrected by the SEAT, not the dev — and the dev was right to refuse. My dispatch brief told the dev to fix the PR body. That instruction was wrong: ② FAIL BASIS 2 is fixed, and the replacement is stronger than the claim it replaces. The body no longer says "byte-identical on both halves at both refs". It now states what was measured: half ① ( ③ FAIL BASIS 1 is fixed in FOUR carriers, not three. The three in-tree ones were rewritten by the dev, which re-verified #16577 first-hand rather than taking the review's word ( ④ ⛔ The review's "10th drift row" correctable is WRONG on both halves, and no edit was made for it. I verified this myself against the live body rather than accepting either side:
⇒ correcting that "count" would have meant inventing a claim in order to correct it. ⛔ Not done, and the re-review should not raise it again. ⑤ Still open, carried deliberately and not silently: Scope fence held: exactly the three named files, +26/−9, one commit. No severity moved, no schema moved, no assertion added or removed (62 tests before and after), Generated by Claude Code |
Contract reviewServed-tier: 192/192
① Derived judgmentsAll seven dispatch items were completed. Worktrees were cut detached from head 1. FAIL BASIS 1 — DISCHARGED in all three in-tree carriers, and stated accurately. 2. FAIL BASIS 2 — DISCHARGED; the replacement text is what was measured. 3. The fourth carrier — corrected, and the correction is TRUE. 4. Nothing broken. 5. Scope fence — CLEAN. Exactly 3 files versus merge base: the changeset, 6. CI at this head — 35 distinct NAMES, by job conclusion, latest run per name. 49 check runs returned of 49 total (no pagination gap), deduped by newest start time. 30 success, 5 skipped, 0 failure, 0 cancelled, 0 queued or in progress. This matches the seat's note, and it was verified rather than accepted. ② Semver level
What ③ Boundary flags⛔ No fail basis at this head. Both prior bases are discharged, and each was re-derived rather than adopted. Correctable, ⛔ not fail bases:
Settled items re-derived and NOT raised. The "10th drift row" correctable is indeed wrong: four probes over the live body ( The round did what a fix round should: it corrected prose in the only direction the evidence supports, scoped the ruling to the route the ruling actually settled, found a fourth stale carrier the previous review missed, declined two review items that did not survive measurement and said why, and moved nothing executable — verified by executing the module rather than by reading it. Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #17445
Clause-②: no
What was wrong
Two carriers in
packages/spec/src/kernel/functional-completeness.tsasserted the same renderer behaviour, and objectui deleted it:VIEW_BINDING_BLOCKS' calendar row —calendargoes tostartDateField || 'start_date',endDateField || 'end_date'— stated as measured, against "the built console 17.2.0";checkViewCompletenessemits on theview/layout-without-bindingroute: "the renderer falls back to literal default field names ... the view renders empty while authoring reports success".The re-verification — mine, at my own ref
⛔ The card's measurement (objectui
3fbdd4a2d, 2026-09-10) was not taken on trust. Re-read on objectuimainat0cf2d6644(2026-09-21), and again at the pin this repo builds against,.objectui-sha=87af769e9(2026-09-20). Both measured facts hold at both refs; only half ① is byte-identical. Half ①,packages/plugin-list/src/ListView.tsx, is byte-identical across the two refs —git log 87af769e9..0cf2d6644 -- packages/plugin-list/src/ListView.tsxis empty, and so is the file diff. Half ②,packages/plugin-calendar/src/ObjectCalendar.tsx, is not: 43 insertions / 17 deletions, one commit, objectuiafb228418(objectui#10101, routing the component's copy through the locale packs). What the finding rests on survives that commit exactly:getCalendarConfigoccupies lines 287–313 at both refs with an md5-identical body (3424cd63d0300a8ad7c3f78c233f742f), and the refusal sentence is character-identical at both refs — objectui#10101 moved it out of JSX text into the English default oftt('calendar.configRequired', …), so the rendered string is unchanged at the pin this repo ships while a non-English locale on objectui's head renders other words. The in-tree docblock already had the accurate wording ("Both reads are identical at the pin"); this paragraph now matches it rather than over-claiming past it:packages/plugin-list/src/ListView.tsx,case 'calendar':— the two literal floors are gone. The branch resolvesstartDateField/endDateField/titleFieldfrom the declared block only and spreads each conditionally; its comment records objectui#7029 and the reason ("field names no view had written and most objects do not carry").packages/plugin-calendar/src/ObjectCalendar.tsx—getCalendarConfig(schema)returnsnullwith neither acalendarblock nor a flatstartDateField, and theif (!calendarConfig)arm renders the refusal screen.Two deltas against the card, both in the same direction:
titleFieldis not required, which matchesCalendarConfigSchemaon this side (one required key:startDateField).dateFieldrung the card's sentence named is retired (objectui#8355);getCalendarConfigreads the declared spellings only.⇒ the claim under test is confirmed dead, and confirmed dead in the tree this repo ships, not just in objectui's head.
What this PR changes
VIEW_BINDING_MESSAGEcarries the calendar entry; the other five types receive the unchanged generic body throughunboundBlockMessage. A type that stops flooring gets an entry, never a reworded universal.calendar.startDateField(the block's one required key) and records that the event title resolves through the ADR-0079 display-name chain whentitleFieldis omitted. Thefixhint is unchanged.appearance.allowedVisualizationsONLY —type: 'calendar'with nocalendar:block parses CLEAN at all three doors #16577 ruled B on 2026-09-11 (comment5634033966, director seat, class-one self-adjudication, summon chore(ci)(deps): bump actions/upload-artifact from 4 to 6 #22; the card closedcompleted): thetype: 'calendar'route stays warning-class under ADR-0078 §1, carried atwarningbycheckViewCompleteness'sVIEW_BINDING_BLOCKS. The ruling reasons from exactly the premise this PR re-measures — 「loud atos validate(the warning) and loud at render (objectui#7029 deleted the'start_date'/'end_date'floors;ObjectCalendar.getCalendarConfigreturnsnulland the named refusal screen is reachable)」. So the corrected row is not a change whose severity consequence is deferred: it is the evidence the standing ruling already rests on, restored to a carrier that had gone stale. The three in-tree carriers that deferred to spec(ui): the list-view calendar guard gates onappearance.allowedVisualizationsONLY —type: 'calendar'with nocalendar:block parses CLEAN at all three doors #16577 as an open question (functional-completeness.ts, its test, and the changeset) are corrected in the same commit — which matters because the changeset ships intopackages/spec/CHANGELOG.md, the exact file where the previous stale claim survived.Per-row verdict for the rest of
VIEW_BINDING_BLOCKSEvery row was re-read at
0cf2d6644, because this lane's recurring failure is patching a region and leaving the neighbour:kanban— STILL TRUE.groupByFieldthengroupFieldthendetectStatusField(objectDef); the row's "inferred" is that detector.calendar— FALSE. Corrected here.gantt— FALSE, recorded and not corrected. All four floors are gone (objectui#7070 for the dates, objectui#7499 forprogressField/dependenciesField) andObjectGanttREFUSES an absent date binding rather than drawing the "blank chart" the row describes.timeline— HALF FALSE, recorded. Thecreated_atfloor was deleted by objectui#7070 step three on the ruling 日期轴永不虚构, with a refusal screen in its place; thetitleField || 'name'half of the row still stands.map— FALSE, recorded.locationField || 'location'is gone on BOTH faces (objectui#8169) andObjectMaprenders "Map configuration required".functional-completeness.test.ts, so correcting that row means correcting message and pin together — which is why it is not a rider here.tree— STILL TRUE.treeCfg.labelField || treeCfg.titleField || 'name', withparentFieldauto-detected when omitted.gallery, the recorded non-row, is stilltitleField || 'name'— unchanged.)The three stale rows are recorded in the table's own re-measurement note so nobody reuses them as current fact, and reported for filing. Correcting them is a separate finding: each changes what its row's severity rests on — the same class of question #16577 settled for
calendar(ruled B, closedcompleted2026-09-11), decided one route at a time and on that route's own measurement. #16577 is closed; it owns no open docket forgantt/timeline/map.Validation — every command with its exit code
Exit codes captured before any pipe (redirect first,
EXIT=$?second — never across a pipe). Head for the block below:6f1b457d48.Fix round, head
4530d21191— prose-only re-run of every derived family that reads what the fix touched, each exit code captured before any pipe:pnpm --filter @objectstack/spec run check:objectui-pin-citations— 0 ("29 asserting objectui pin citation(s) match .objectui-sha (87af769e9), 48 historical citation(s) recorded and not checked, across 1511 spec source(s)"; 7 anchor content assertions verified, i.e.ASSERTED_ANCHOR_FLOORuntouched) ·pnpm --filter @objectstack/spec exec vitest run src/kernel/functional-completeness.test.ts— 0 (62 passed) ·pnpm --filter @objectstack/spec typecheck— 0 ·node scripts/check-spec-docblock-symbol-anchors.mjs— 0 ·node scripts/check-issue-citations.mjs --base origin/main— 0 ·pnpm check:nul-bytes— 0 ·node scripts/check-comment-mask-adoption.mjs— 0 ·node scripts/check-keyed-text-bounds.mjs— 0 ·node scripts/check-empty-changeset.mjs --base origin/main— 0 ·node scripts/check-changeset-no-major.mjs --base origin/main— 0 ·node scripts/check-adr-0087-registration.mjs --base origin/main— 0 ·node scripts/check-closing-keyword-parity.mjs— 0 ·pnpm check:pm-prior-rulings— 0 ·pnpm exec eslint --no-inline-config --format jsonover the two changed sources — 0, 2 files / 0 errors / 0 warnings.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ranreconciles that record as 82 derived, 11 run, 0 NOT-MEASURED, 71 UNRUN — the 71 are CI's farm, a declared narrowing, not a silent one.pnpm --filter @objectstack/spec build— 0pnpm --filter @objectstack/spec exec vitest run src/kernel/functional-completeness.test.ts— 0 (62 passed)pnpm --filter "@objectstack/lint^..." build --concurrency=2thenpnpm --filter @objectstack/lint exec vitest run src/validate-functional-completeness.test.ts— 0 (25 passed). The first attempt at that suite exited 1 onFailed to resolve entry for package "@objectstack/formula"— a PREREQUISITE NOT MET, not a red gate: the dependency closure was unbuilt. Re-run after building it.pnpm --filter @objectstack/spec typecheck— 0pnpm --filter @objectstack/spec check:generated— 0 (15 generated artifacts up to date)pnpm exec eslint --no-inline-configon both changed files — 0, 0 errors / 0 warnings over 2 files (counted from--format json). The repo-widepnpm lintis CI's run; this narrowing is measurable rather than assumed becauseeslint.config.mjsdeclares noparserOptions.projectand no typed rules, so a diff in these two files cannot move the verdict on any file it does not contain.pnpm check:nul-bytes— 0; plus a direct control-character scan of the three touched files, no hits.pnpm --filter @objectstack/spec check:objectui-pin-citations— 0; the asserting-citation count rose 28 to 29, i.e. the new pin citation is recognised and checked against.objectui-sha, so the next pin bump fails loudly on this record.node scripts/check-spec-docblock-symbol-anchors.mjs— 0pnpm check:issue-citations— 0. That alias is the--self-testhalf (package.json:84spells itnode scripts/check-issue-citations.mjs --self-test). The diff-scoped half isnode scripts/check-issue-citations.mjs --base origin/main, run on the fix commit — 0, 13 citations judged across 1 file (3 resolve, 1 resolves-as-pull-request, 9 cross-repo-unjudged). CI runs both halves atlint.yml:4893(pnpm check:issue-citations && node scripts/check-issue-citations.mjs) inside "Lint & Repo Gates". ·node scripts/check-changeset-no-major.mjs --base origin/main— 0 ·node scripts/check-adr-0087-registration.mjs --base origin/main— 0 ·node scripts/check-closing-keyword-parity.mjs— 0 ·node scripts/check-comment-mask-adoption.mjs— 0 ·pnpm check:pm-prior-rulings— 0 ·node scripts/check-changeset-fixed.mjs— 0Gate families were derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(77 commands); the rest of that list is CI's — nothing here is claimed for a command that was not run.Acceptance notes
functional-completeness.test.tsand@objectstack/lint'svalidate-functional-completeness.test.tsassert rule ids, severities, paths andfixcontent; the only message pin on this route is the map one quotinglocationField || 'location', which this PR leaves alone. A stale prose comment in the spec test'sdescribeheader did restate the deleted premise for all six types — corrected, and two pins added: the calendar body must not claim a literal fallback and must namecalendar.startDateField, and the other five types must keep receiving the generic body (an honest pin: it records which body a type gets, not that the body is true of it).VIEW_BINDING_FIX.calendaris left untouched. It suggestsstartDateFieldplus the optionaltitleField, whichCalendarConfigSchemaaccepts and the renderer honours, so it is still correct — the card says so too.CHANGELOG.mdfiles carries the same "renderer falls back to literal default field names" sentence. Released entries are amended only in a dedicated docs-only PR, never as a rider here.🤖 Generated with Claude Code
https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Generated by Claude Code