Skip to content

fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names - #17877

Draft
os-bill wants to merge 3 commits into
mainfrom
claude/issue-17054-calendar-config-alldayfield
Draft

fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names#17877
os-bill wants to merge 3 commits into
mainfrom
claude/issue-17054-calendar-config-alldayfield

Conversation

@os-bill

@os-bill os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17054

CalendarConfigSchema now declares allDayField, the fifth field binding on a calendar config.

The two sentences, and which one was wrong

The object-calendar door refuses a flat allDayField and prescribes, verbatim from its own diagnostic:

Write this as a key of the calendar config object instead — calendar: { startDateField, endDateField, titleField, colorField, allDayField }.

CalendarConfigSchema was a strictObject of 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 with git show PIN:path, not at that checkout's HEAD):

  • packages/plugin-list/src/ListView.tsxcollectViewFields reads v.allDayField off schema.calendar and schema.options.calendar at two sites, feeding the $select projection and the $expand set. The authored nested key already changes what the server is asked for.
  • The same file's case 'calendar': branch spreads ...(schema.calendar || {}) onto the object-calendar node, so the nested key reaches the block.
  • packages/plugin-calendar/src/ObjectCalendar.tsxgetCalendarConfig resolves it into the calendar config.
  • packages/app-shell/src/views/ObjectView.tsx — the dev-mode Spec Compliance warning lists allDayField among the flat keys an author must move under viewDef.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's allDayField, for one), and stripping them here would silently disable a shipped capability."
  • Post-pin, on objectui 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 main carries a comment declaring allDayField objectui-LOCAL, in the same class as its sanctioned defaultView, and concluding "honouring allDayField widens 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: defaultView is the renderer's initial view mode, a UI preference that already has a declared home as an object-calendar component prop; allDayField is a field binding, the same kind as its four neighbours, and it had no home at all. defaultView stays 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.calendar is z.unknown(), so the block accepts calendar: { …, allDayField } today. The second refusal lands one door over, on stored view metadataListViewShapeSchema.calendar is CalendarConfigSchema — 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 ./ui export.

input door before after
{ objectName, allDayField } flat object-calendar REFUSED unrecognized_keys keys=["allDayField"] REFUSED, unchanged
calendar: { four, allDayField } object-calendar ACCEPTED ACCEPTED
{ four, allDayField } CalendarConfigSchema REFUSED unrecognized_keys keys=["allDayField"] ACCEPTED
calendar: { four, allDayField } ListViewSchema REFUSED unrecognized_keys at path: ["calendar"] ACCEPTED
{ four } CalendarConfigSchema ACCEPTED (positive control) ACCEPTED
{ four, bogusKeyXy } CalendarConfigSchema REFUSED (negative control) REFUSED

The exact refusal texts, before:

  • flat, on object-calendar: Unrecognized key(s) on this \object-calendar`: `allDayField`.` followed by the prescription quoted above.
  • nested, on the config: 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.zod and ./component.zodsrc/, not dist/, 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 allDayField on object-calendar (one key per concept, and the refusal still carries the prescription); defaultView on the config, so the opening is exactly one key wide; an unknown key, in the same message shape, at the config and at path: ["calendar"]; and startDateField is still required, so allDayField alone is not a calendar binding.

Ablation

Mutation: rename the declaration to allDayFieldAblated in packages/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 text 0 → 1, and the file's git hash-object moving 3ecc02a254265786fc29c146408479ed072ee462 → 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 names allDayField, 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 hash 3ecc02a254265786fc29c146408479ed072ee462 equal to the HEAD blob, with an empty-hash guard treating a missing read as FAILURE, and git diff HEAD empty.

Changeset

.changeset/17054-calendar-config-all-day-field.md, grade minor — a published accept set widens, and minor is the floor for this class. Not skip-changeset, measured rather than assumed, with npm pack --dry-run --json after a build and controls in both directions over the packed file list (2012 files):

  • subject allDayField52 published files.
  • positive control startDateField (a sibling key that must publish) → 55.
  • negative control bogusKeyXy, which lives only in the new test file → 0.
  • the new test file is absent from the packed list, and 0 *.test.ts files publish at all.
  • ⚠️ packages/spec also ships src/**/*.zod.ts as 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/main merged via scripts/pm/os-regen-merge.sh before opening; main brought driver-sql and lint changes only, no packages/spec, and no contact with PR #17796's ui/view.zod.ts hunks — 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: test is not the whole suite.
  • pnpm --filter @objectstack/spec typecheck :: exit 0 — including check:test-typecheck (shrink-only ledger held).
  • pnpm --filter @objectstack/spec check:generated :: exit 0 — all 15 generated artifacts up to date; authorable-surface/ui.json gained exactly one line, ui/CalendarConfig:allDayField, and authorable-surface.base.json was not touched.
  • pnpm lint (eslint . --no-inline-config, repo-wide) :: exit 0 — no narrowing claimed.
  • Derived families via 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 (--ran reconciliation exits 0). Exit codes captured before any pipe.
  • NOT MEASURED, declared, not green: pnpm check:dual-build-cjs-loads and pnpm 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.
  • Control bytes: 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-review rides 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.calendar is z.unknown(), so the component door validates nothing about the config it names in its own .describe(). Tightening it to CalendarConfigSchema would 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.
  • objectui's mirror comment and its ObjectCalendar docblock both state that allDayField is 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.
  • The objectui-side list-view-spec-parity pin lists defaultView as 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7cab0d8723b2be6cc5fd08c60b527a0f31b84edbpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 2ea2254e8cf827a110487c4413d173c34b64376f — the merge of head 7803e3d6fd3def538d9ee33ba1609c2d3921c5de into base 7cab0d8723b2be6cc5fd08c60b527a0f31b84edb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

os-contract-review

ESCALATE TO MAINTAINER

The 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 protocol:ui carrier, which is on the standing manual floor. ⭐ Q3 is the maintainer's question, and it is the only thing between this head and the queue.


Q1 — Is the widening warranted? YES. The key is honoured by a real consumer at the pin, with one scope correction.

Read at .objectui-sha = 53ded82bf7a494f54e344e19099dbf00854b8694, with git show PIN:path — ⛔ not at the objectui checkout's HEAD, which sits on an unrelated branch.

  • packages/plugin-list/src/ListView.tsx:1466v.colorField, v.allDayField, inside collectViewFields, fed by collectViewFields((schema as any).calendar) at :1481 and ((schema as any).options?.calendar) at :1482. This collector builds the $expand set.
  • packages/plugin-list/src/ListView.tsx:1820 — the same candidate list in the projection collector, fed from schema.calendar at :1833 and schema.options?.calendar at :1834, each name passed to addSpeculative at :1829. This is the $select projection. ⇒ an authored nested allDayField already changes what the server is asked for, at the pin.
  • packages/plugin-list/src/ListView.tsx:2448...(schema.calendar || {}), in case 'calendar': (:2423) spreads the authored block onto the object-calendar node, so the nested key reaches the renderer.
  • packages/plugin-calendar/src/ObjectCalendar.tsx:126-128 returns the authored schema.calendar wholesale from getCalendarConfig; :137 carries allDayField: (schema as any).allDayField on the flat-fallback branch.
  • packages/app-shell/src/views/ObjectView.tsx:2085'progressField', 'colorField', 'allDayField', 'subjectField', 'endField', in the dev-mode flat-key list, warning text at :2092-2093: a third face telling the author to move the key under viewDef.calendar.
  • packages/types/src/zod/objectql.zod.ts:346-349 — the .passthrough() rationale naming this key: "the renderers grow config knobs ahead of the protocol (calendar's allDayField, for one), and stripping them here would silently disable a shipped capability." The mirror itself is at :357-362.

Control (the instrument could have come back empty, and did). Same command, same file, same pinned blob: allDayField → count 2, rc 0; colorField → 3, rc 0; bogusKeyXy0, rc 1; defaultViewXyz0, rc 1.

⚠️ One scope correction to the PR body's strongest-sounding sentence. At the pin the renderer does not consume the resolved key: packages/plugin-calendar/src/ObjectCalendar.tsx:555 is allDay: !endDate, // If no end date, treat as all-day event — the inference alone, with allDay appearing at exactly two lines in that file (:137, :555). The semantic honouring lands post-pin, on objectui origin/main at packages/plugin-calendar/src/ObjectCalendar.tsx:684allDay: allDayField ? Boolean(record[allDayField]) : !endDate. The PR body and the changeset both label that reading "post-pin", so this is accurate as written, not overstated — but the precise claim that survives is: at the pin the key is honoured in the fetch projection, the handoff and the config resolution; the drawn-output honouring is one objectui release ahead. That is still "recording reality", not inventing surface.

Claim 2 — is the defaultView distinction real, or a rationalisation? It is real, and it is checkable in this repo. packages/spec/src/ui/component.zod.ts:2946 declares defaultView: z.enum(['month', 'week', 'day']).optional().describe('Initial view mode') on ObjectCalendarPropsSchema. So defaultView already has a declared spec home as an object-calendar component prop, and allDayField had none anywhere. The countervailing docblock (objectui origin/main, packages/plugin-calendar/src/ObjectCalendar.tsx:155-183) puts the two keys in one class from the objectui side — which is true of what objectui needed in order to honour them — but the two are demonstrably not one class from the spec side, because one of them is declared here and the other was not. ⇒ the round's distinction holds; I do not read it as a rationalisation.


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 CalendarConfigSchema at this head:

  • SHAPE_KEYS = ["startDateField","endDateField","titleField","colorField","allDayField"] — exactly five.
  • Base, git show origin/main:packages/spec/src/ui/view.zod.ts, the block at :1360: exactly four (startDateField, endDateField, titleField, colorField). ⇒ +1, and only +1.
  • Control, lit and returning the other way: the same single safeParse fed nine extra names came back REFUSED_KEYS = ["defaultView","bogusKeyXy","allDay","isAllDayField","allDayFields","AllDayField","alldayfield","timeZoneField","locationField"]. Case variants and near-spellings are all still refused, so no spelling tolerance rode in with the key, and an "everything accepted" result was reachable and did not happen.

Anything else the diff lets through: nothing. git diff --stat origin/main...7803e3d6fd is 7 files — one schema source, one test, one generated line (packages/spec/authorable-surface/ui.json, +1), three generated .mdx, one changeset. The added to the calendar summary cells in content/docs/references/api/protocol.mdx and content/docs/references/data/object.mdx is generated truncation, not surface. The one honest framing note: the key is admitted at every site that embeds CalendarConfigSchema — the generated docs show the row landing in three view shapes (content/docs/references/ui/view.mdx:110, :947, :1344). That is what declaring a member means, not an extra opening.

Ablation A — cost direction, the "one key wide" pin. Mutation in my own worktree: add a second key (defaultView) to CalendarConfigSchema.

  • On-disk proof read FIRST: git hash-object packages/spec/src/ui/view.zod.ts = 3ecc02a254265786fc29c146408479ed072ee462, identical to HEAD:packages/spec/src/ui/view.zod.ts; declaration occurrences defaultView 0, injected probe text 0.
  • After mutation: hash d4e0cb032703ffeab703836e46994879c28c5e49, defaultView declaration 1, injected probe text exactly 1 (the script aborts otherwise).
  • Predicted RED. Observed RED: exit 1, Tests 1 failed | 8 passed (9), and the one failure is exactly the case named "REFUSES defaultView on the calendar config, still — the opening is exactly one key wide", at packages/spec/src/ui/calendar-config-allday-prescription-17054.test.ts:132.
  • Restore proven by hash, not by an exit code: back to 3ecc02a254265786fc29c146408479ed072ee462, git diff HEAD empty, re-run GREEN 9 passed (9), exit 0.

Ablation B — cost direction, the lead class pin. Mutation: drop allDayField out of the prescription literal in packages/spec/src/ui/component.zod.ts:2921-2922.

  • Before: hash 65eb6d491b12e9879238bafa03c7c127e0e9ae47, equal to the HEAD blob; the edit asserts a unique match before writing. After: hash c277ec43d44bfbad3da59f22f9d73c20b6d76004, grep -c 'colorField, allDayField }' moved 1 → 0.
  • Predicted RED. Observed RED: exit 1, 1 failed — the lead pin, the case named "every key the prescription names inside calendar: { … } is accepted by CalendarConfigSchema". Restore: hash back to 65eb6d491b12e9879238bafa03c7c127e0e9ae47, git diff HEAD empty.
  • Worth stating: the flat-refusal pin did not flip under B, because my mutation trimmed the key list and left the prescription sentence standing. That is correct scoping of that pin, not a gap.

⇒ 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: 9 passed (9), exit 0.

Claim 4 — is the round's correction of the dispatch statement itself correct? YES. packages/spec/src/ui/component.zod.ts:2944 is calendar: z.unknown().optional(), whose own .describe() at that line already spells the five-key config. So the object-calendar door accepts the prescribed shape today, before and after; the second refusal lands one door over, on stored view metadata through ListViewShapeSchema.calendar. The round corrected the seat rather than inheriting the convenient framing, and the corrected framing is the accurate one. The trap is undiminished: stored view metadata is how calendars are actually authored.


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: 改动扩大接受集或公开面 ⇒ 人工;拉回已声明契约 ⇒ 代裁车道 (.claude/skills/pm-dispatch/SKILL.md:391, :397; .claude/skills/pm-dispatch/references/core-rules.md:96). Measured against that test, not argued:

  1. The accept set demonstrably widened — base 4 keys, head 5, verified by direct shape read, and a stored view that was refused now parses.
  2. The published authorable surface grew: packages/spec/authorable-surface/ui.json gained ui/CalendarConfig:allDayField. The PR carries protocol:ui and a minor changeset, so this publishes.
  3. It is 难回滚 in the operative sense. Withdrawing a published authorable key later is not a revert; it runs the enforce-or-remove retirement machinery (ADR-0049 route choice, ADR-0087 disposition, baselines, forms, pins). Adding it costs one line; removing it costs that entire procedure.

⭐ 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 duplicate into this card by triage on 2026-09-10 — says of exactly this change: "adding a member to a published strictObject is an accept-set widening with its own review requirements" and "It is still a widening of a published accept set and is not mine to order." Downstream, objectui#8831 is an open decision card (pm:queue, p2) whose option (a) is explicitly gated on this ruling: "This card should not be ruled before objectstack#17140 is." So landing this head does not merely fix a card — it settles a two-repo contract direction that two seats declined to settle.

The counterweight, stated so the maintainer sees both: triage routed #17054 into pm:queue as a p2 bug with the instruction "Decide which spelling is real and make both the schema and the diagnostic say it", not into the decision box; and the seat's own clause-② declaration named needs:contract-review as the instrument. So an at-tier review was the right instrument for the measurement — and that half passes without conditions. It is not the right instrument for the authority.

What I certify: the measurement. What I decline: the direction. Route this to the decision box.

维护者速读(一个问题)

日历视图的「是否全天」绑定键 allDayField,前端已经在读(拉取字段、配置透传都走它,渲染端在 objectui 主干上已经按它画图),错误提示、组件文档和设计器也一直照着这个拼法教作者写;但协议 schema 从来没有承认过这个键 —— 作者照着报错原文写,在保存视图那一道门会被第二次拒绝。这一轮把两句话里哪句错的量到了:错的是 schema,不是提示。

分歧只剩一件事:协议要不要正式收下这个键。收下,就是公开可写面多一项、发 minor,以后再想撤要走完整的属性退役流程;不收,就得改掉报错文案,这个键继续留在前端私有面,协议永远不认它,下游 objectui#8831 按「协议不收」的方向收尾。

A. 收下 —— 按本 PR 落地(schema 增一个键,文档与公开面同步,objectui#8831 转为「上游已收」)。
B. 不收 —— 退回改提示文案那条路,键留在 objectui 本地。


Q4 — Is anything owed that is missing? One small cross-repo notice. Nothing blocking.

  • Changeset. .changeset/17054-calendar-config-all-day-field.md, grade minor. Correct: nothing is renamed or removed, so no ADR-0087 disposition and no FROM → TO migration are owed (AGENTS.md:1038-1049), and minor is the right floor for an additive accept-set widening rather than patch.
  • Generated artifacts. packages/spec/authorable-surface/ui.json gained exactly one line, ui/CalendarConfig:allDayField. I ran pnpm --filter @objectstack/spec check:generated at this head myself: 14 of 15 green, 1 stale — check:api-surface, exit 1. ⚠️ That is not this PR's. Control: the same command on a fresh origin/main worktree in the same unbuilt state also reports ✗ check:api-surface, exit 1, with the other 14 green — so the probe distinguishes, and the staleness is build-dependent/pre-existing. Second control: api-surface/ carries no field-level key at all (colorField, the sibling that must be there if it did, greps to 0 matches), so this key could not have moved it.
  • authorable-surface.base.json untouched — correct, not an omission. It is the deletion gate's anchor, re-anchored only by the manual-only gen:authorable-surface-base; check-generated prints it as "Explicit, manual-only (1) … never run here or by --fix". A newly added key legitimately does not appear there.
  • Pin citations. pnpm --filter @objectstack/spec check:objectui-pin-citations :: exit 0 — 14 asserting citations match .objectui-sha 53ded82bf, so the new TSDoc block's pin reference is validated rather than asserted. Declared gap the gate itself prints: 0 anchor content assertions verified (no objectui checkout at the path it looks for), and it states no anchor carries a quoted first line yet, so nothing was silently skipped.
  • Docs. Generated and consistent: the row lands at content/docs/references/ui/view.mdx:110, :947, :1344, and the three summary cells gained .
  • Tombstone / guidance text. Nothing is removed, so none is owed. The object-calendar flat-key guidance at packages/spec/src/ui/component.zod.ts:2914-2926 is unchanged and now points somewhere real — and ablation B proves the pin on it is live.
  • Sibling pins. I ran the four other spec test files that touch CalendarConfig together with the new one: src/ui/view.test.ts, src/ui/component.test.ts, src/ui/view-gantt-tree-config-closed-15469.test.ts, src/type-alias-convention.pin.test.ts, src/ui/calendar-config-allday-prescription-17054.test.ts :: exit 0, 5 passed (5) / 720 passed (720).
  • ⚠️ The one thing owed and missing. objectui#8831 is open and correct as the named carrier for the two now-stale objectui comments — but its body still says its option (a) is blocked "until objectstack#17140 rules", and finding(spec): OBJECT_CALENDAR_FLAT_FIELD_KEYS' prescription tells authors to write allDayField inside calendar: {} — but CalendarConfigSchema is strict and refuses it by name #17140 was closed as a duplicate into this card. Nothing on this PR or on card spec: ComponentPropsMap['object-calendar'] prescribes calendar: { ..., allDayField } — a shape its own CalendarConfigSchema refuses by name #17054 tells that card its gate has moved here. That is a one-comment cross-repo notice, owed after the direction is ruled, not before — ⛔ writing it now would announce a ruling this tier did not make.

What I did NOT measure

  • The round's full-suite numbers. I did not re-run pnpm --filter @objectstack/spec test (claimed 473 files / 13443 tests), test:repo (30 / 520), typecheck, or repo-wide pnpm lint. I ran the new pin file, the four sibling CalendarConfig files, check:generated and check:objectui-pin-citations only. Those claims are NOT MEASURED by me — not disputed, not confirmed.
  • The built-dist before/after table. My readings are all against src/, which is what the pins import. The PR's table was taken through the built ./ui export; I did not rebuild, so the dist half is NOT MEASURED by me.
  • The publish-surface counts (npm pack --dry-run, 2012 files, 52/55/0). Not re-run.
  • The derived-gate reconciliation (scripts/pm/dispatch-gates.mjs, 106/104/2/0) and the two declared exit 3 gates. Not re-run; their verdicts remain CI's.
  • Runtime behaviour. Everything about objectui is a code reading at a git object. No app was booted, no calendar was rendered, and nothing here proves an authored allDayField draws an all-day band in a browser.
  • Whether origin/main has moved under this head since 7cab0d8723. I measured the head as it stands.

Tier statement

This 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: ComponentPropsMap['object-calendar'] prescribes calendar: { ..., allDayField } — a shape its own CalendarConfigSchema refuses by name

2 participants