Skip to content

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

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-17054-calendar-config-alldayfield
Sep 13, 2026
Merged

fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names#17877
os-bill merged 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

os-bill commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review ADOPTED — verdict ACCEPT-WITH-NOTES, at tier (exact)

Adopted by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-13T08:12Z. The review below is reproduced verbatim; the seat added nothing to it and removed nothing from it.

Tier verified by the seat, not taken from the dispatch parameter

⛔ A model: dispatch parameter is configuration, not a reading. The tier was read out of the round's own transcript, from the harness-stamped served-model field on every assistant turn (taken 2026-09-13T08:11:58Z):

transcript served model census
this review round (a0a2a6230656bc070) claude-fable-5-1 ×75 — and no other model, zero turns
control: the os-dev implementation round on this branch (aa3b8a559934912f5) claude-opus-5 ×109

CONTRACT_REVIEW_TIER read at scripts/pm/dispatch-gates.mjs:10176'claude-fable-5-1'. Comparison is exact, never a family or prefix floor. ⇒ at tier. The control transcript is there so a census that returns one value everywhere cannot be mistaken for an instrument that only ever returns that value.

This PR is one of the rounds named by #17915 (contract reviews dispatched below CONTRACT_REVIEW_TIER). It is the one that was still open, so the re-run was preventive rather than post-hoc — and it earned its cost: the at-tier round found Q4, which the below-tier round missed. See the seat's disposition below.

Follow-ups from the verdict — both discharged before this adoption

  1. Cross-repo notice to objectui#8831 (ruling item 2, the seat's act) — posted: decision(types,plugin-calendar): objectui teaches the flat calendar field spellings as authorable; upstream Prime Directive #12 says they are read-only — reconcile objectui#8831 comment 5652138403, 2026-09-13T08:11:01Z. It names all four assertions and both stale docblocks, at line numbers this seat re-verified itself against objectui origin/main 69aa9c017f527926ea11718c37e6583eeb18b5f1. Two corrections to the verdict's own line citations, from that re-reading: the refusal block in calendar-doc-key-set-8830.test.ts is :178-186 (assertions at :181, :184, :185), not :180; and in calendar-flat-color-allday-8466.test.ts the shape equality spans :416-421 and the nested refusal :422-426. The verdict's :175 and :416 are exact. Neither correction changes the finding.
  2. api-surface/ staleness — accepted as pre-existing and build-dependent, on the strength of the verdict's lit control (the same check exits 1 on the merge-base worktree in the same unbuilt state, with this PR absent). ⛔ No card filed from this PR.

Carriers

needs:contract-review hangs on both this PR and card #17054. The verdict returns PASS on measurement and the direction is settled by the ruling (os-tesla, card comment 5651571942), so both are being cleared now — two removals, seconds apart. A single removal would be a strip, not a clear.

⛔ The seat records, as the verdict itself asks: this round is an isolated subagent under the same seat session that dispatched the os-dev round. It is not an independent second seat.


Contract review — PR #17877, head 7803e3d6fd3

Tier self-check (first finding)

  • Reading: scripts/pm/dispatch-gates.mjs:10176export const CONTRACT_REVIEW_TIER = 'claude-fable-5-1'; (2026-09-13T07:52:56Z). Docblock at :10170: "served tier is EXACT, never a family or prefix floor".
  • Served model, harness-stamped in this transcript: claude-fable-5-1. EXACT match. No model dispatch parameter was taken as a reading.
  • Independence pair: this round is an isolated subagent under seat session session_01MkQhmuuJAVDjmeWNixwDDH, which also dispatched the os-dev round. Implemented-by: mode:subagent, branch claude/issue-17054-calendar-config-alldayfield. Reviewed-by: the seat that adopts this verdict. Not an independent second seat — stated so the seat records it verbatim.

The prior round (5647331529, 2026-09-12T16:56Z) was read as premises only. Its findings were re-measured below; three of its cited line numbers held, one thing it missed is Q4.


Q1 — Clause ② judgment: yes is right.

Readings (07:57:01Z merge base 7c2c5aed, 07:57:04Z head 7803e3d6; tsx probe over src/ui/view.zod.ts + component.zod.ts):

mb:   SHAPE_KEYS ["startDateField","endDateField","titleField","colorField"]   allDayField ABSENT
head: SHAPE_KEYS ["startDateField","endDateField","titleField","colorField","allDayField"]   optional? true
C2 four+allDayField  mb: REFUSED unrecognized_keys ["allDayField"]  → head: ACCEPTED
L2 ListView(calendar:{four+allDayField})  mb: REFUSED path ["calendar"] → head: ACCEPTED

Rule text (07:54:46Z): references/lanes/spec.md:19 「放宽接受集或扩大公开面的卡,不论多小,即条款②;收窄仍是语义面,不触条款②」; references/contract-review.md:13 「新导出符号或已发布载荷上的新键恒 yes」. Control grep zzqq_nonexistent → 0.

What widens: one key, allDayField: z.string().optional(), on CalendarConfigSchema — reachable through every published payload that embeds it (ListViewShapeSchema.calendar and the two other view shapes, generated docs rows at view.mdx:110/:947/:1344). Published: packages/spec files includes src/**/*.zod.ts and authorable-surface/ui.json gains ui/CalendarConfig:allDayField. Additive (Q3). node scripts/pm/check-clause2-carriers.mjs --pair 17877 → exit 0, both carriers agree (07:57:24Z).

Q2 — Implements the ruling, not its summary: yes, and nothing beyond it.

Ruling, os-tesla, card comment 5651571942, 2026-09-13T06:10:17Z, verbatim: maintainer 「16678 具体解释,计划用哪个字段判断经理。其他同意」 — 「其他同意」 covers this card (batch #127 item 2 = A). Execution clauses:

  1. "PR fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names #17877 is the delivery: allDayField joins the strictObject, authorable-surface/ui.json gains its one line, minor changeset." — diff: view.zod.ts +28 (one key + TSDoc), ui.json +1 line, .changeset/17054-calendar-config-all-day-field.md "@objectstack/spec": minor. ✓
  2. "Cross-repo notice to objectui#8831 in the same round"seat's act, not the PR's. objectui#8831 comments read 07:56:26Z: only the triage comment; notice not yet posted. Owed (see notes).
  3. "Nothing else moves: the flat spelling stays a runtime handoff … ⛔ not a second authorable spelling."component.zod.ts untouched (diff = 7 files, none is it); probe O1 flat allDayField on object-calendar: REFUSED at both mb and head, prescription text present at both. ✓

Scope the ruling does not name: 1 test file (pins, no contract effect) and 3 generated .mdx (regenerated output, Q6). No unrequested scope.

Q3 — Additivity: proven.

Probe: every input ACCEPTED at mb is ACCEPTED at head (C1, L1, O2); flips are exactly C2/L2 (REFUSED→ACCEPTED); negative controls unchanged (C3 bogusKeyXy, C4 defaultView, L3, O1). allDayField absent → C1/L1 parse identically at both. Required anywhere? No — C5 (allDayField alone) still fails on startDateField at both. One shape change worth naming: C6 allDayField: 1 refuses as invalid_type (was unrecognized_keys) — still refused, no narrowing. Ablation (08:03:52Z, under lock, merge-base worktree, test blob hash 57db78d4… identical to head blob): PR's pin file vs the mb schema → 3 failed | 6 passed (9), exactly the three acceptance cases, lead pin failing on "the prescription names allDayField, which CalendarConfigSchema refuses". Restore: 0 dirty paths.

Q4 — Blast radius: in-repo clean; ⚠ two objectui pins flip on the next spec bump — unnamed by the PR and by the prior round.

In-repo sweep (07:55:00Z, 07:56:26Z): files naming CalendarConfig outside spec src — packages/lint/src/validate-page-visualization-bindings.ts (comment only), examples/app-crm/src/views/activity.view.ts (four keys, no pin). Files naming startDateField in tests outside spec: 4 (lint/metadata-protocol) — all fixture values, no key-set/count pins. Control: colorField in corpus → 14 files; toBe(4) in spec src → 16. Liveness ledger: keyed per type, CalendarConfig rows 0, control colorField 0 — no row owed. state-counts.md regenerated → unchanged. authorable-surface.base.json untouched: correct (manual-only anchor).

objectui origin/main (69aa9c0), absent at pin 53ded82b, present on main:

  • packages/types/src/__tests__/calendar-doc-key-set-8830.test.ts:175 expect(listed).toEqual(Object.keys(CalendarConfigSchema.shape).sort()) and :180-182 refused.success toBe(false) for allDayField.
  • packages/types/src/__tests__/calendar-flat-color-allday-8466.test.ts:416 Object.keys(CalendarConfigSchema.shape)).toEqual([four]), :422-423 nested allDayField success toBe(false).

Both red the moment objectui installs a spec carrying this key (objectui main lockfile: @objectstack/spec@17.4.0). Control: list-view-spec-parity.test.ts:186 derives from the spec shape and does not flip — the PR's "nothing to do" claim covers only that file. Not a blocker here (different repo, lands on the bump), but it is the content the owed objectui#8831 notice must carry.

Q5 — Prescription: one spelling, exact match.

component.zod.ts:2918 keys list and :2921-2922 prescription literal calendar: { startDateField, endDateField, titleField, colorField, allDayField }; :2945 .describe('… allDayField? }') — optional, ships to component.mdx:312. Declared key allDayField, z.string().optional(). Spelling census in spec src excluding the PR test: allDayField ×7, no allDay/isAllDay/all_day variants (07:55:07Z).

Q6 — Generated artifacts: regenerated, not hand-edited.

Worktree at head: gen:schema (exit 0, 1534 schemas), gen:docs (exit 0, 222 files), gen:liveness-counts (exit 0) → git status --porcelain empty (07:58:52Z). check:generated: 14/15 green, api-surface/ stale. Lit control: check:api-surface on the merge-base worktree, same unbuilt state, PR absent → also exit 1 (07:59:30Z); api-surface/ carries 0 colorField (control CalendarConfigSchema 1); PR touches api-surface/ not at all. ⇒ build-dependent, pre-existing, not this PR's.

Q7 — CI, newest run per name, head 7803e3d6.

All 39 check runs completed; conclusions success or skipped. Lint & Repo Gates (job 103585518873) success, ran through its last step (check:duration-unit-keys OK at 16:58:46Z) — no first failure, no exit-3 shape, nothing unmeasured behind it. filter job: core=true (spec files) so Test Core 6/6 shards + aggregate ran on head. Duplicate names: Auto Label / Check PR Size skipped in run 24492 (16:37:56Z) — the same workflow's run 24491 (16:37:32Z, same sha) succeeded; skips are conditional jobs, not failures. PR-side CI is the affected subset — Q4 sweep covers the full-suite question for this repo.

Q8 — Prose invariants.

  • TSDoc/changeset: collectViewFields reads allDayField — pin ListView.tsx:1466/:1481/:1820/:1833 ✓; calendar branch spreads ...(schema.calendar || {}) :2448 ✓; getCalendarConfig :126-128/:137 ✓; .passthrough() rationale objectql.zod.ts:348 ✓; component.mdx:312 ✓.
  • .describe(): "a record whose flag is true is drawn as an all-day band …; absent or false is not all-day; omit → no end date draws as all-day" — backed by objectui main ObjectCalendar.tsx:739 allDay: allDayField ? Boolean(record[allDayField]) : !endDate. At the pin :555 is allDay: !endDate — the drawn-output half is post-pin. Backed, correctly labelled post-pin in the TSDoc; note only.
  • Changeset "minor is the floor for this class" ✓ (AGENTS.md:1040-1049; nothing removed → no ADR-0087 disposition owed).

Q9 — Changeset: owed, present, correct.

npm pack --dry-run --json (07:56:22Z, unbuilt so dist absent — irrelevant to the src glob): src/ui/view.zod.ts → 1 (positive), calendar-config-allday-prescription-17054 → 0, *.test.ts → 0 (negative), authorable-surface/ui.json → 0 (not published; docs/api-surface are). files = ["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts",…].


Verdict: ACCEPT-WITH-NOTES

Follow-ups (no new file changes owed on this PR):

  1. Cross-repo notice to objectui#8831 (ruling item 2, seat's act, not yet posted) — must name calendar-doc-key-set-8830.test.ts:175,:180 and calendar-flat-color-allday-8466.test.ts:416,:422 as the first reds on the next @objectstack/spec bump, plus the two stale docblocks.
  2. api-surface/ staleness is pre-existing/build-dependent — no card from this PR.

Clause-② carriers: this round is at tier (exact) and returns PASS on measurement; direction is settled by the ruling. The carriers clear when the seat adopts this verdict verbatim and posts it (I wrote nothing to GitHub).

NOT MEASURED


Generated by Claude Code

os-bill commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Served-tier: 75/75 claude-fable-5-1

Verdict record in the ruled shape — ACCEPT-WITH-NOTES

Re-posted by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-13T08:26Z, to carry the Served-tier: line in the fixed spelling. The full review record is comment 5652145198 on this PR; it is unchanged and remains the substance. This comment exists because the ruling on #17915 (os-tesla, comment 5651924370, item 1) says "a verdict without the line is not a verdict", and my adoption comment carried the reading in a table rather than in the fixed spelling the carrier-strip check (item 2) is to be written against. A gate cannot read a table.

field value
Served-tier 75/75 claude-fable-5-1 — census of the harness-stamped served-model field over every assistant turn of the reviewer's transcript, taken 2026-09-13T08:11:58Z
CONTRACT_REVIEW_TIER 'claude-fable-5-1', read at scripts/pm/dispatch-gates.mjs:10176
comparison EXACT — equal
below-tier turns 0
control a different transcript (an os-dev round this session) read claude-opus-5 ×109 — so the probe can return non-fable, and the zero above is a reading, not a dead instrument
verdict ACCEPT-WITH-NOTES
carriers cleared 08:12:39Z (PR) and 08:12:41Z (card #17054) — two removals, seconds apart

⛔ The reading is not the dispatch model parameter. ⛔ It is not a bare model-name token grepped out of the transcript body — that probe has a documented false-positive mode (objectui seat, comment 5651573578 on #17915: an adopted verdict quoted back into a later round plants a model-name token in its transcript, so the false-positive rate grows with adoption of this very discipline, biased toward falsely voiding good rulings). This census parses each transcript line as JSON, keeps only records whose type is assistant, and reads the structured message.model key — the harness stamp itself, never prose.


Generated by Claude Code

Merged via the queue into main with commit 38472ce Sep 13, 2026
44 checks passed
@os-bill
os-bill deleted the claude/issue-17054-calendar-config-alldayfield branch September 13, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

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