fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names - #17877
Conversation
…iption already names
The object-calendar door refuses a flat `allDayField` and prescribes
`calendar: { startDateField, endDateField, titleField, colorField, allDayField }`,
and that block's `calendar` prop `.describe()` publishes the same five-key shape
to the generated reference docs. `CalendarConfigSchema` was a strictObject of
four keys and refused the prescribed shape by name, so an author who followed
the diagnostic verbatim on a stored view was refused a second time, by a
different schema, with a different message.
Measured which half was wrong rather than picking: the key is honoured, not
inert. At the objectui pin this repo builds against, ListView's
`collectViewFields` reads `calendar.allDayField` into the fetch projection and
its calendar branch forwards the authored block onto the object-calendar node,
where `getCalendarConfig` resolves it; objectui made it load-bearing in the
render itself. It is a field binding like its four neighbours, which is what
separates it from `defaultView` -- a UI preference that keeps its own declared
home as an object-calendar component prop and stays refused here.
Pinned in both directions: the prescribed shape is accepted at the config
schema and through the stored-view door, and the flat spelling, `defaultView`
and an unknown key are all still refused. The lead pin reads the key list out
of the prescription the runtime prints and asks the config schema to accept
each one, so a future diagnostic naming a non-member goes red on the class.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…e changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…lendar-config-alldayfield
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. 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 2ea2254e8cf827a110487c4413d173c34b64376f && git checkout 2ea2254e8cf827a110487c4413d173c34b64376f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7cab0d8723b2be6cc5fd08c60b527a0f31b84edb 7803e3d6fd3def538d9ee33ba1609c2d3921c5de && git checkout -B drift-repro 7cab0d8723b2be6cc5fd08c60b527a0f31b84edb && git merge --no-ff 7803e3d6fd3def538d9ee33ba1609c2d3921c5de
node scripts/docs-audit/affected-docs.mjs --json 7cab0d8723b2be6cc5fd08c60b527a0f31b84edb |
|
os-contract-review ESCALATE TO MAINTAINERThe round's work holds. I tried to break it and could not: Q1, Q2 and Q4 pass, the cost-direction pins are load-bearing under ablation, and the round's correction of its own dispatch statement is itself correct. What this tier cannot supply is the authority. This change widens a published accept set and grows the published authorable surface on a Q1 — Is the widening warranted? YES. The key is honoured by a real consumer at the pin, with one scope correction.Read at
Control (the instrument could have come back empty, and did). Same command, same file, same pinned blob:
Claim 2 — is the Q2 — Is the opening exactly one key wide? YES, and the cost pins are load-bearing.Direct shape reading, not inferred from the diff. Probing
Anything else the diff lets through: nothing. Ablation A — cost direction, the "one key wide" pin. Mutation in my own worktree: add a second key (
Ablation B — cost direction, the lead class pin. Mutation: drop
⇒ five of nine cases are the cost direction, and two of them are measured load-bearing by ablation rather than asserted. Baseline at this head before any mutation: Claim 4 — is the round's correction of the dispatch statement itself correct? YES. Q3 — Does this belong on the manual floor? YES. It is the maintainer's, not this tier's.The standing floor hands the maintainer 功能新增, ADR, 协议/公开契约变化, 破坏性或难回滚动作, and its mechanical boundary test is explicit: 改动扩大接受集或公开面 ⇒ 人工;拉回已声明契约 ⇒ 代裁车道 (
⭐ And the direction itself is contested in writing, by the people who filed it, which is the signature of a ruling rather than a fix. The upstream twin objectstack#17140 — closed The counterweight, stated so the maintainer sees both: triage routed #17054 into ⇒ What I certify: the measurement. What I decline: the direction. Route this to the decision box. 维护者速读(一个问题)日历视图的「是否全天」绑定键 分歧只剩一件事:协议要不要正式收下这个键。收下,就是公开可写面多一项、发 A. 收下 —— 按本 PR 落地(schema 增一个键,文档与公开面同步,objectui#8831 转为「上游已收」)。 Q4 — Is anything owed that is missing? One small cross-repo notice. Nothing blocking.
What I did NOT measure
Tier statementThis is an in-seat at-tier review — the adjudicating subagent inherits the dispatching seat's session id, so it is ⛔ NOT an independent second seat, and it was dispatched with an explicit model parameter. Generated by Claude Code |
Fixes #17054
CalendarConfigSchemanow declaresallDayField, the fifth field binding on a calendar config.The two sentences, and which one was wrong
The
object-calendardoor refuses a flatallDayFieldand prescribes, verbatim from its own diagnostic:CalendarConfigSchemawas astrictObjectof exactly four keys and refused that shape by name.The round measured which half was wrong rather than picking the convenient one, and the answer is (a) — the schema was missing a key that is honoured. It is not (b): trimming the prescription would leave a shipped, honoured capability with no protocol carrier.
The evidence, read at the objectui pin this repo builds against (
.objectui-sha=53ded82bf7a494f54e344e19099dbf00854b8694, read withgit show PIN:path, not at that checkout's HEAD):packages/plugin-list/src/ListView.tsx—collectViewFieldsreadsv.allDayFieldoffschema.calendarandschema.options.calendarat two sites, feeding the$selectprojection and the$expandset. The authored nested key already changes what the server is asked for.case 'calendar':branch spreads...(schema.calendar || {})onto theobject-calendarnode, so the nested key reaches the block.packages/plugin-calendar/src/ObjectCalendar.tsx—getCalendarConfigresolves it into the calendar config.packages/app-shell/src/views/ObjectView.tsx— the dev-mode Spec Compliance warning listsallDayFieldamong the flat keys an author must move underviewDef.calendar: a third face prescribing the nested spelling.packages/types/src/zod/objectql.zod.ts— the mirror keeps.passthrough()and names this key as its reason: "the renderers grow config knobs ahead of the protocol (calendar'sallDayField, for one), and stripping them here would silently disable a shipped capability."main, the renderer makes it load-bearing:allDay: allDayField ? Boolean(record[allDayField]) : !endDate.⭐ A widening is not made acceptable by the diagnostic having promised it. This one is right because the renderer honours the key — the prescription merely happened to be the accurate half.
The countervailing reading, stated plainly. objectui
maincarries a comment declaringallDayFieldobjectui-LOCAL, in the same class as its sanctioneddefaultView, and concluding "honouringallDayFieldwidens no accept set". That is a true statement about what objectui needed in order to honour it, and it does not bind what the protocol may declare. The two keys are not the same class from this side:defaultViewis the renderer's initial view mode, a UI preference that already has a declared home as anobject-calendarcomponent prop;allDayFieldis a field binding, the same kind as its four neighbours, and it had no home at all.defaultViewstays refused on this config, pinned.A correction to the card's framing, measured
The card reads as though the prescribed shape is refused at the door that printed the prescription. It is not.
ObjectCalendarPropsSchema.calendarisz.unknown(), so the block acceptscalendar: { …, allDayField }today. The second refusal lands one door over, on stored view metadata —ListViewShapeSchema.calendarisCalendarConfigSchema— which is how calendars are actually authored in this product. The trap is real; the two doors are just not the same door. Both readings are in the before/after table.Measured against the BUILT dist, before and after
Build first and confirm both passes finished (
check-dts-emitted: 34/34 declared declaration file(s) present), then parse through the package's own./uiexport.{ objectName, allDayField }flatobject-calendarunrecognized_keyskeys=["allDayField"]calendar: { four, allDayField }object-calendar{ four, allDayField }CalendarConfigSchemaunrecognized_keyskeys=["allDayField"]calendar: { four, allDayField }ListViewSchemaunrecognized_keysatpath: ["calendar"]{ four }CalendarConfigSchema{ four, bogusKeyXy }CalendarConfigSchemaThe exact refusal texts, before:
object-calendar:Unrecognized key(s) on this \object-calendar`: `allDayField`.` followed by the prescription quoted above.Unrecognized key(s) on this calendar configuration: \allDayField`. Until these shapes were closed an unknown key was dropped silently — the view still rendered, without whatever the key was meant to configure.`After, the first is byte-identical and the second is gone — replaced by acceptance. The bogus-key control still produces that second text verbatim with
keys=["bogusKeyXy"], which is what proves the message did not change, only the membership.Pins, both directions
packages/spec/src/ui/calendar-config-allday-prescription-17054.test.ts, 9 cases. They import./view.zodand./component.zod—src/, notdist/, so no rebuild leg is needed for the ablation, and that is measured rather than assumed.Accepted: the prescribed shape at the config schema; the same shape through the stored-view door where the second refusal used to land; the same view without the key as a control.
⭐ The lead pin is written on the defect CLASS, not on one key: it reads the key list out of the
calendar: { … }shape the runtime's own prescription prints and asks the config schema to accept each name, with a floor on the extracted list so an empty extraction cannot make it vacuously true. Any future diagnostic that names a non-member goes red here, including a key nobody has thought of yet.Still refused — what the widening did NOT cost: the flat
allDayFieldonobject-calendar(one key per concept, and the refusal still carries the prescription);defaultViewon the config, so the opening is exactly one key wide; an unknown key, in the same message shape, at the config and atpath: ["calendar"]; andstartDateFieldis still required, soallDayFieldalone is not a calendar binding.Ablation
Mutation: rename the declaration to
allDayFieldAblatedinpackages/spec/src/ui/view.zod.ts. Absolute paths,trap '…' EXIT INT TERM.On-disk proof read FIRST, before the run: declaration occurrences
1 → 0, injected text0 → 1, and the file'sgit hash-objectmoving3ecc02a254265786fc29c146408479ed072ee462 → 560dc1d3299460e582e04d0a727a89600e6d23ba. The run then aborts itself if the injected text is not present exactly once.Predicted direction: RED. Observed:
3 failed | 6 passed (9), exit 1 — exactly the three acceptance pins, with the lead pin failing on its own sentence: "the prescription namesallDayField, which CalendarConfigSchema refuses". GREEN after restore:9 passed (9), exit 0.Restore proven by hash, not by an exit code:
git checkout HEAD -- ABSOLUTE_PATH(never the bare form, which reads the index), restored hash3ecc02a254265786fc29c146408479ed072ee462equal to the HEAD blob, with an empty-hash guard treating a missing read as FAILURE, andgit diff HEADempty.Changeset
.changeset/17054-calendar-config-all-day-field.md, grademinor— a published accept set widens, andminoris the floor for this class. Notskip-changeset, measured rather than assumed, withnpm pack --dry-run --jsonafter a build and controls in both directions over the packed file list (2012 files):allDayField→ 52 published files.startDateField(a sibling key that must publish) → 55.bogusKeyXy, which lives only in the new test file → 0.*.test.tsfiles publish at all.packages/specalso shipssrc/**/*.zod.tsas source and its tsup does not strip comments, so a source comment is published text: the probe phrase from the new TSDoc block appears in 23 published files. Controls were picked accordingly.Purely additive — nothing that parsed before is refused now, and no key is renamed or removed, so there is no ADR-0087 disposition to declare.
Verification
Head
7803e3d6fd.origin/mainmerged viascripts/pm/os-regen-merge.shbefore opening; main brought driver-sql and lint changes only, nopackages/spec, and no contact with PR #17796'sui/view.zod.tshunks — that PR is not addressed here and remains open.Every number below is from the final head, after the merge.
pnpm --filter @objectstack/spec test(--project local) :: exit 0 — 473 files / 13443 tests, 0 skipped.pnpm --filter @objectstack/spec test:repo(--project repo, the cross-corpus scanners) :: exit 0 — 30 files / 520 tests. ⭐ Run separately on purpose:testis not the whole suite.pnpm --filter @objectstack/spec typecheck:: exit 0 — includingcheck:test-typecheck(shrink-only ledger held).pnpm --filter @objectstack/spec check:generated:: exit 0 — all 15 generated artifacts up to date;authorable-surface/ui.jsongained exactly one line,ui/CalendarConfig:allDayField, andauthorable-surface.base.jsonwas not touched.pnpm lint(eslint . --no-inline-config, repo-wide) :: exit 0 — no narrowing claimed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived on the merged head and identical to the pre-merge derivation: 106 derived, 104 run green, 2 NOT MEASURED, 0 unrun (--ranreconciliation exits 0). Exit codes captured before any pipe.pnpm check:dual-build-cjs-loadsandpnpm check:type-check-debt, both exit 3 — PREREQUISITE NOT MET. Each needs every workspace package built (turbo run build --filter='./packages/*' --filter='./packages/*/*'), which is the whole-farm run CI owns; nothing about them is answerable from a spec-only closure. This is a declared narrowing, not a skipped gate, and their verdicts are CI's.pnpm check:nul-bytes:: exit 0, plus a direct scan of all seven changed paths for the wider control-byte class — no match, grep exit 1.Clause-②: yes — this widens a published accept set, so the round's measurement agrees with the value declared at dispatch.
needs:contract-reviewrides on both carriers and this does not enqueue without an at-tier verdict on the head that lands.验收备注
Out of scope, noted and not filed — each with its carrier named:
ObjectCalendarPropsSchema.calendarisz.unknown(), so the component door validates nothing about the config it names in its own.describe(). Tightening it toCalendarConfigSchemawould narrow a published accept set and needs its own ruling; it is not a defect, it is an unbuilt door. Carrier: whoever next converges the component-door configs.ObjectCalendardocblock both state thatallDayFieldis not a spec key. Once this lands, both are stale. objectui#8831 is already the declared follow-up and triage named it, so this is not a new card. Carrier: objectui#8831.list-view-spec-paritypin listsdefaultViewas the only sanctioned local key on the calendar config; the mirror derives from the spec schema, so it picks up this key without an edit. Nothing to do, recorded so the next reader does not go looking. Carrier: objectui#8831.Generated by Claude Code