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 |
Contract review ADOPTED — verdict
|
| 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
- 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 objectuiorigin/main69aa9c017f527926ea11718c37e6583eeb18b5f1. Two corrections to the verdict's own line citations, from that re-reading: the refusal block incalendar-doc-key-set-8830.test.tsis:178-186(assertions at:181,:184,:185), not:180; and incalendar-flat-color-allday-8466.test.tsthe shape equality spans:416-421and the nested refusal:422-426. The verdict's:175and:416are exact. Neither correction changes the finding. 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:10176→export 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. Nomodeldispatch parameter was taken as a reading. - Independence pair: this round is an isolated subagent under seat session
session_01MkQhmuuJAVDjmeWNixwDDH, which also dispatched theos-devround.Implemented-by: mode:subagent, branchclaude/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:
- "PR fix(spec): declare allDayField on CalendarConfigSchema, the key the object-calendar prescription already names #17877 is the delivery:
allDayFieldjoins thestrictObject,authorable-surface/ui.jsongains its one line,minorchangeset." — diff:view.zod.ts+28 (one key + TSDoc),ui.json+1 line,.changeset/17054-calendar-config-all-day-field.md"@objectstack/spec": minor. ✓ - "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).
- "Nothing else moves: the flat spelling stays a runtime handoff … ⛔ not a second authorable spelling." —
component.zod.tsuntouched (diff = 7 files, none is it); probe O1 flatallDayFieldonobject-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:175expect(listed).toEqual(Object.keys(CalendarConfigSchema.shape).sort())and:180-182refused.success toBe(false)forallDayField.packages/types/src/__tests__/calendar-flat-color-allday-8466.test.ts:416Object.keys(CalendarConfigSchema.shape)).toEqual([four]),:422-423nestedallDayFieldsuccess 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:
collectViewFieldsreadsallDayField— pinListView.tsx:1466/:1481/:1820/:1833✓; calendar branch spreads...(schema.calendar || {}):2448✓;getCalendarConfig:126-128/:137✓;.passthrough()rationaleobjectql.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 mainObjectCalendar.tsx:739allDay: allDayField ? Boolean(record[allDayField]) : !endDate. At the pin:555isallDay: !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):
- 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,:180andcalendar-flat-color-allday-8466.test.ts:416,:422as the first reds on the next@objectstack/specbump, plus the two stale docblocks. 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
- Full
pnpm --filter @objectstack/spec test(473/13443),test:repo,typecheck, repo-widelint— I ran 5 files / 720 tests + the ablation only. - Built-dist before/after table;
npm packafter build (dist half);check:dual-build-cjs-loads,check:type-check-debt. - Issue finding(spec):
OBJECT_CALENDAR_FLAT_FIELD_KEYS' prescription tells authors to writeallDayFieldinsidecalendar: {}— butCalendarConfigSchemais strict and refuses it by name #17140 body; the prior round's ablations A/B (mine was a different one); objectui test execution; any browser rendering.
Generated by Claude Code
|
Served-tier: 75/75 Verdict record in the ruled shape —
|
| 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
… /api/v1/meta/view, not a buried invalid_union sub-error (objectstack-ai#17900) Fixes objectstack-ai#17299 Clause-②: no — the same body parses or refuses identically before and after; only the 422's top-level message moves. Answered as measurements below: **zero new exported symbols** and **zero new published-payload keys**, each with a lit control. ## What was wrong `ViewMetadataSchema` is the union behind `PUT /api/v1/meta/view` — the door objectstack-ai#13100 measured as the one an MCP/AI author actually reaches, with no CLI anywhere on the path. A shape-level refusal raised inside one of its four branches did not become the union's message: the top level read zod's bare `Invalid input`, and the prescription sat at `error.issues[0].errors[k][j].message`. Re-run first on this branch's own base (`origin/main` `a61ae59f93`), against the built artifact, with the card's own repro: ``` ### CARD REPRO — list overlay virtualScroll top.code: invalid_union top.message: "Invalid input" errors[2][0] code=invalid_type expected=never path=["virtualScroll"] msg="`view.virtualScroll` was removed in @objectstack/spec 17.0.0 (ADR-0049 …" ``` Shipped behaviour, not a regression: `virtualScroll`, `striped` and `bordered` have read this way since 17.0.0. ## Reach — one pin per union branch, named Triage's ruling: fixing the top-level message for one branch is not the deliverable. All four branches were measured burying a tombstone **before** the change and surfacing it **after**, each landing in its own claimed-branch slot (so `focusClaimedBranch` is selecting the branch the pin names): | branch | tombstone exercised | before | after | |---|---|---|---| | `viewItem` | `config.virtualScroll` | `Invalid input` | `` `view.virtualScroll` was removed … `` | | `container` | `list.striped` | `Invalid input` | `` `view.striped` was removed … `` | | `listOverlay` | `virtualScroll` (the card's repro) | `Invalid input` | `` `view.virtualScroll` was removed … `` | | `formOverlay` | `aria` | `Invalid input` | `` `form.aria` was removed … `` | Pinned in `packages/spec/src/ui/view-union-retirement-prescription.test.ts` §1, which also asserts `selectViewMetadataBranch(body)` equals the branch each pin claims — a pin surfacing the right text from the wrong branch would measure nothing about reach — and asserts the lifted string is byte-identical to the nested issue it came from. ## Per case or family-wide — decided by measurement **Family-wide.** `retiredKey()` is `z.never({ error: () => guidance }).optional()`, so a tombstone raises one declared issue shape: `code: 'invalid_type'`, `expected: 'never'`, `message` = the prescription. The union's existing objectstack-ai#7510 `.check()` now lifts that message verbatim from the claimed branch. The measurement that permitted it — and the reason a naive family-wide lift would have been wrong: - `strictObject()` closes a shape with a `z.never()` **catchall**, so the union's four members reach **67** `never` leaves in the schema graph and only **8** are tombstones. - The other **59** never raise the lifted shape: zod folds a rejecting `never` catchall into `code: 'unrecognized_keys'` instead. Measured on the built artifact — a bogus key under `pagination` reports `unrecognized_keys` with its curated prose, never `expected: 'never'`. - **The control is lit by construction**: those 59 *are* `z.never()` in the graph, so a discriminant reading the graph would have caught all 67. Reading the raised **issue** catches exactly the 8. §2 walks the population out of the schema graph rather than listing it (so a retirement added tomorrow joins the pin with nobody editing it) and fails if either population is empty; §3 fails if any catchall ever starts lifting. Why not per case: `exportOptionsPdfUnionError` (objectstack-ai#8010) re-reads `issue.input` structurally, which is right for an enum **value** narrowing — it leaves no tombstone to key on. A **key** retirement does leave one, and there are eight on this surface reachable at four different depths (`virtualScroll`, `config.virtualScroll`, `list.virtualScroll`, `listViews.KEY.virtualScroll`), which a structural input-reader would have to re-implement the whole nesting to find. There are **592** `retiredKey()` call sites in this package; the symptom is the buried messages, but the defect is that the next retirement reproduces it silently. ## Sibling objectstack-ai#17320 — one convention does not cover both objectstack-ai#17320 landed today as `2d34f320d11`. It wired a `{ error }` callback into seven **leaf** `filter` doors so a bare `invalid_type` *says* something. This card is the other half: the message already exists and is already correct at the leaf, and the **union wrapper** hides it. Different mechanism, different level — triage's "sibling, not folded" holds. They are complementary rather than overlapping: objectstack-ai#17320 makes a leaf message worth reading, and a lift that surfaced a bare leaf message would only have raised bare text to the top. ## What must not change, and did not - A **plain shape error** still reads `Invalid input`; an unknown list-view type, an empty container and a body claiming nothing all keep zod's message, and none acquires "was removed" text (§4). - A **curated unknown-key refusal** still reads exactly as it did, and the top-level message stays `Invalid input` (§3). - **The acceptance face did not move.** The lift runs inside the objectstack-ai#7510 `.check()`, after the union has reached its verdict, and writes one string. Verdicts and issue codes stay pinned by the corpora in `view-union-branch-focus.test.ts` and `view-union-diagnostics.test.ts`, untouched here and green. ## Ablation — mutate on disk, prove it landed, RED, restore, prove by hash From the committed state. HEAD blob for `view.zod.ts`: `046557c80e9d285158311253935091a15dcabb37`. **Leg A, discriminating** — delete the lift. Occurrence count 1 to 0; on-disk hash moved to `08e8f65c89bb…`. Result **RED: 5 failed / 10 passed**. Restored: hash back to `046557c80e9d…`, `git diff HEAD` empty. **Leg B, cost direction** — make the lift unconditional (drop the `expected === 'never'` discriminant, lift any claimed-branch message). Occurrence count 1 to 0; hash moved to `32928cada68d…`. Result **RED: 2 failed / 13 passed**, and the two it killed are exactly the cost-direction pins: ``` FAIL §3 catchall control — so its curated prose stays where it was and the top-level message is untouched FAIL §4 cost direction — an unknown list-view type keeps zod's bare union message ``` Restored: hash back to `046557c80e9d…`. Both legs ran under a `trap` restoring against `HEAD` at an absolute path. ## Clause-② (a) — new exported symbols: ZERO, with a lit control `retirementPrescription` is deliberately **module-private**, exactly as `focusClaimedBranch` and `exportOptionsPdfUnionError` beside it are. Read from the **regenerated** artifacts, not from memory, after `pnpm --filter @objectstack/spec build`: - **Lit control** — appending one dummy `export const` to the same file, rebuilding and regenerating, **moved** `packages/spec/api-surface/ui.json`, and the probe name greps out of it. The probe works and is aimed at the artifact that records `ui/` exports. - **Real answer** — control removed, rebuilt, regenerated: `git status --porcelain packages/spec/api-surface packages/spec/export-origins` is **empty**. `check:api-surface` and `check:export-origins` both green with no regeneration owed. ## Clause-② (b) — new published-payload keys: ZERO, with a measurement `check:authorable-surface` (authorable-surface/ + authorable-defaults/ + the JSON schemas) is green with nothing to regenerate, so no authorable key moved in either direction. `check:generated` reports all 15 generated artifacts up to date. The publish surface itself was measured with `npm pack --dry-run --json` in `packages/spec` (2012 files) against two controls: the edited source `src/ui/view.zod.ts` **does** ship (the manifest's `files[]` carries `src/**/*.zod.ts`) alongside `dist/ui/*`, while both test files correctly do **not** — so the listing discriminates. That is what makes the change consumer-visible and the changeset owed. ## Changeset `.changeset/17299-view-union-retirement-prescription.md`, graded **patch** against this repo's own precedent: objectstack-ai#17320's changeset, landed today for the same class of change ("the change is to what a refusal says"), is `'@objectstack/spec': patch`. The population is real, not an empty one read as clean — the file was committed before the gates ran, and they counted it: `check:empty-changeset` reports *1 declaring changeset(s) added* and `check:adr-0087-registration` reports *1 non-breaking changeset(s) seen*. ## Verification Both `packages/spec` vitest projects, run separately, exit codes reported separately: | run | exit | result | |---|---|---| | `pnpm --filter @objectstack/spec test` (project `local`) | **0** | 473 files passed, 1 skipped; 13463 tests passed, 1 skipped | | `pnpm --filter @objectstack/spec test:repo` (project `repo`) | **0** | 31 files passed; 523 tests passed | | `pnpm --filter @objectstack/spec typecheck` | **0** | test layer compiles; debt ledger held | | `pnpm --filter @objectstack/spec check:generated` | **0** | all 15 generated artifacts up to date | Derived gate families — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 81 commands over the real change set: **77 exit 0**. The remaining **4 are exit 3 = NOT MEASURED, not green and not red** — every one prints `PREREQUISITE NOT MET` because a sibling package has no `dist/` in this worktree, where only `@objectstack/spec` was built: `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:type-check-debt`, `check:doc-formula-expressions`. None of them reads this diff's files; all four are declared to CI, which builds the whole repo. One existing pin was repaired rather than deleted. The objectstack-ai#17063 overlay-door pin selected its issue with `message.includes(...)`; the prescription now legitimately appears at two levels, so that find is satisfied by either the wrapper (path `[]`) or the tombstone (path `pageName`), and the pin's subject is the one with a path. It now selects on `expected === 'never'` and keeps the message assertion beside it. The stale comment in `view.test.ts` that left the per-case-vs-family question open now points at where it is settled. ## Overlap, declared The declared file face `packages/spec/src/ui/view.zod.ts` overlaps two parked pieces of work — PR objectstack-ai#17877 (card objectstack-ai#17054) and card objectstack-ai#17507. Declared, worked anyway per this lane's rule; whoever lands second resolves the conflict. No `view.columns` / `view.tabs` / `view.sort` title work is touched here — that is objectstack-ai#17507's subject. --- _Generated by [Claude Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ict declares (objectstack-ai#17990) Fixes objectstack-ai#17915 `CONTRACT_REVIEW_TIER`'s own docblock declares the comparison against the **served** tier EXACT — "never a family or prefix floor" — and nothing in this tree performed it. The dispatching seat passes a model as a dispatch parameter, and a passed parameter is configuration rather than a reading; the docblock's other half said the re-review sub-round's "opening self-check reads this", but a self-check is prose to the reviewer, so a round that simply did not run it produced a verdict indistinguishable from one that did. The census on the card measured 11 rounds served below the declared tier across four days and eleven PRs, five of them the only clearance a merged `Clause-②: yes` pair ever had. This lands items 1–3 of the director ruling: the verdict carries the reading, and the strip is gated on it. ## What changed **`.claude/skills/pm-dispatch/references/contract-review.md`** (2 lines of new rule, net 0 at the ratchet): - 〈复核归属与资格(席内)〉 gains, beside the 同形 definition: > 同形含首行 `Served-tier:`,值取复核者转录的 harness `model` 盖章;无此行不成裁决。 - 〈降档保险丝(机读)〉 turns the prose instruction into the mechanism it described: > 清标前 `--pair`:裁决 `Served-tier:` ≠ `CONTRACT_REVIEW_TIER` ⇒ exit 4,点名 PR、评论、读数。 **`scripts/pm/check-clause2-carriers.mjs`** — a new finding row, **C7**: - `readServedTier` reads a `Served-tier:` key line with `Reviewed-by:`'s own discipline (case-sensitive key, the markdown decoration a seat writes without meaning it). Three-valued: `read` / `unreadable` / `missing` — a carrier never started and one started and left unreadable are different facts. - ⭐ The value grammar is `[N/M ]tier`, **corrected from a fixture against the live board** before shipping. Every record the ruling's own remediation rounds are posting right now spells it control-first — `Served-tier: 75/75 \`…\`` on PR objectstack-ai#17877, `138/138` on objectstack-ai#17498 — and the ruling's specimen is written the same way. A reader that demanded the tier token immediately after the colon would have refused every verdict produced under the rule it enforces, on day one. The `N/M` is judged rather than skipped: it is the zero-hit control the discipline already requires, and a count that is not total is the 「回退证据」 whose own rule text voids a verdict entire. Absent, it is vacuous — the tier alone decides, which is the ruling's minimum, so nothing the ruling permits is refused. - `reviewOfRecord` carries the reading on the record it already chose, so C6 and C7 can never disagree about which verdict a clear stands on. - `c7ServedTierBelow` fires on **C6's population and no other** — `needsRecordRead`'s completed state, i.e. a clearance judgment of a hung carrier: declared `yes`, the gate bound and cleared on both carriers, head unmoved. The refusal names the PR, the verdict comment and the served value, at exit 4 (a limb not standing), never 3. - The comparison is EXACT against `CONTRACT_REVIEW_TIER`, **imported** from `dispatch-gates.mjs` so the model id keeps exactly one value site across `scripts/pm/**` and `.claude/skills/pm-dispatch/**`. No model identifier appears anywhere in this diff outside that import. - 42 self-test cases in a new battery: the ruling's three (at tier green, below tier red, line missing red), the live value shape and its bulleted/bolded spelling, the control pins (`0/0` void, `12/133` refused as fallback evidence, absent vacuous, a perfect control never rescuing a below-tier value), the exactness pins (a family prefix and an extended value both refused), and the four populations the row must never reach. ### Deliberately NOT in scope A `Clause-②: no` pair that never carried the label is never refused for lacking the line — it is not in the candidate shape, so the row cannot reach it. A pair still carrying the gate owes nothing yet. An absent or unsigned record stays C6's row alone. No PASS/FAIL token is read to reach any of it: what produced a verdict is measurable, what it concluded stays human. ## One deviation from the dispatched file surface — and why it is inside it The dispatch scoped the diff to those two files. It is those two files — but one edit inside the checker was not foreseen and is worth reading before approving. `scripts/pm/check-clause2-carriers.mjs` carried a `dispatch-gates: no-path-population` marker: "this gate reads no file in the tree at all … so **no card's file surface can predict it**". The input half is still exactly true. The other half stopped being true the moment C7's import landed: a card editing `CONTRACT_REVIEW_TIER` moves the value every clearance is judged against, so it *does* predict this gate. `pnpm check:pm-dispatch-gates` catches this directly — its live-half case `no family both DECLARES no path population and names paths anyway` went red on `check:pm-clause2-carriers`, measured by ablation (base tree: hints `[]`; with the import: hints `[".github/workflows"]`). Keeping a declaration that stopped being true is the exact shape C7 itself exists against, so the marker is **retired**, and the comment left in its place states the trade rather than hiding it: the import channel contributes a followed module's *own* literals, so this family now also inherits a `.github/workflows` lead it never opens, on a gate whose CI step runs the self-test only. The designed narrowing (`inherited-population`, declared by the followed module) cannot express this case — it is per-**module**, and the same module's globs *are* a real population for `check:pm-widening-tells`, which reads them. Filed separately as objectstack-ai#17991 rather than worked around here. ⛔ The alternative — restating the tier in this file — is the thing that let the declared tier and the served one drift apart in the first place, and is refused. ## Acceptance measurements All taken at `fed29ced`, against base `9ccc4179`. | reading | before | after | |---|---:|---:| | `Served-tier` in `references/contract-review.md` | 0 | 2 | | `Served-tier` in `check-clause2-carriers.mjs` | 0 | 26 | | `Reviewed-by` in `references/contract-review.md` (lit control) | 1 | 1 | | `references/contract-review.md` lines | 60 | 60 | | `check-clause2-carriers.mjs` longest line (bytes) | 390 | 390 | - Every line of `references/contract-review.md` is ≤ 120 bytes (`LC_ALL=C awk 'length($0)>120'` prints nothing); the four edited lines measure 105 / 113 / 111 / 108 B. The 120-byte register is the reference file's; the checker keeps its own line style unchanged (471 → 502 lines over 120 B, max unmoved at 390). - `git diff --stat origin/main...HEAD` → exactly `.claude/skills/pm-dispatch/references/contract-review.md` and `scripts/pm/check-clause2-carriers.mjs`. - Line budget, paid by density and not by re-wrap: `+1` the 同形 line, `+1` the mechanism line replacing the 转录档位核验 prose, `-1` by folding 「⛔ 自述档位不是读数」 and 「传参只是配置 ⛔ 不作达档读数」 into one clause (they are one rule: a tier claim that is not a harness stamp is not a reading), `-1` by dropping the 转录核验 grep recipe, whose method survives at :49 (「每场前必读服役档,读法见 `platform-readings.md`」) and in the new 同形 line. `node scripts/pm/check-skill-line-ratchet.mjs` green at 60/60, headroom 0. ## Checks - `pnpm check:pm-clause2-carriers` — **588 cases pass** (546 before this PR; the C7 battery is 42 and is registered with its own floor, `SELF_TEST_BATTERY_FLOOR` 19 → 20, preserving the roster's existing slack). - Gate families derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) on the merged head: **41 families, 41 run, 41 exit 0**. Reconciled: `--ran` reports `41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of them is 3)`. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first answered `PREREQUISITE NOT MET` (exit 3, nothing measured). Built its two declared prerequisites under the shared verify lock (`VERDICT command-exit 0 · held the lock 141s`) and re-ran it: exit 0. - **Reverse verification** (both legs on the committed tree, restored and verified by blob hash): adding the `Served-tier:` requirement turned the reference record fixture red — `⭐ the objectstack-ai#14155 specimen WITH its record still reads CLEAN overall` failed with `["C7"]` — before the fixture gained the line. The row can fail. - **Live control**, `node scripts/pm/check-clause2-carriers.mjs --pair 17956`: exit **2** on this head and exit **2** on the base script for the same PR, same sentence (`PR objectstack-ai#17956 is not open, or names no card this file can derive`). The pair could not be formed, so nothing about it was judged — the dispatch expected 0 for a `Clause-②: no` pair, and that PR has since left the open set. ⛔ Not a C7 refusal, and unmoved by this diff. - Narrowed lint, with its three readings: universe is `eslint .` over one `eslint.config.mjs`; `npx eslint --no-inline-config --format json scripts/pm/check-clause2-carriers.mjs` → 1 file, 0 errors, 0 warnings; the narrowing excludes nothing because that config "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file" (its own line 328), so this diff cannot move any untouched file's verdict. The `.md` is not an eslint input. - `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over both edited files: no hits; `pnpm check:nul-bytes` green. ## Changeset `skip-changeset` — `scripts/pm/**` and `.claude/**` publish nothing: neither path is in any package's `files[]`, and both are on the fast track (`.claude/**` and PM tooling). Label applied and read back. ## Acceptance notes - **Filed as objectstack-ai#17991** — the import channel's over-reach, with both readings: `inherited-population` is keyed on the followed MODULE while fabrication is a property of the CALLER (the same module's globs are a real population for `check:pm-widening-tells`, which reads them), and the `if (entry.selfTest) continue;` guard built to stop exactly this inheritance never fires for a `pnpm check:*` family, because `selfTest` is read off the workflow argv while the `--self-test` lives in the `package.json` script body. Searched first: objectstack-ai#11556 (already resolved, and left alone here) is the same class by a route its remedy cannot express; no open card covers it. - Noted, not filed: nothing else. No other adjacent observation reached a reproducible defect, a quotable contract violation or an authoring trap. ## 维护者速读(草稿) **改了什么** — 契约复核裁决从此必须带一行 `Served-tier:`,值取复核者转录里 harness 逐消息盖的 `model` 字段;`check-clause2-carriers.mjs --pair` 在判定「双载体已清」时读这一行,不等于 `CONTRACT_REVIEW_TIER` 就拒(exit 4),并点名 PR、裁决评论和读到的档位。规则文本同步落在 `references/contract-review.md`,行数 60 → 60。 **为什么改** — 常量自己的 docblock 写着「与服役档的比较是 EXACT」,而树上没有任何东西在比。派进去的 model 是配置不是读数,「开场自检」是写给复核者的散文:一轮不跑它,产出的裁决与跑了的长得一模一样。卡上实测 11 轮在档下产出裁决,其中 5 轮是已合并 `Clause-②: yes` PR 唯一的清标依据。这是本仓在别处一律拒绝的 declared ≠ enforced,落在「一次公共契约加宽到底有没有被复核过」那道门本身。 **风险与代价(含回滚)** — 失败方向是响亮的:清标被拒,不是被静默放行。代价一:规则落地前写的历史裁决没有这一行,再被判定时会红,补救是复核席把自己转录里已经盖好的读数补写成一条新记录(最新的记录优先,不动载体)。代价二:本 PR 让这个门禁第一次有了树内依赖(`CONTRACT_REVIEW_TIER` 所在文件),因此退掉了它「无路径面」的旧声明;派生因此多送一条 `.github/workflows` 的线索,是噪音、已在文件里写明,并已记入验收备注。回滚 = revert 本 PR,一次 revert 即可,门禁回到今天的状态。 **席位意见** — (留空,待席位定稿) **你要做的** — 受管面(`.claude/**`),本 PR 恒为 draft,⛔ 不由任何 AI 席位合并、入队或挂 auto-merge。请人工确认两件事:① 规则文本那两行的措辞;② 退掉 `no-path-population` 声明这一步是否接受(替代方案是把常量在本文件再写一遍,那正是让档位漂移的那个形状,已拒)。 ## Provenance Authored by the `domain:skills` seat's dispatched executor, session `session_01DAcomhvR9kKizeYgg89Vo8` (https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8), on branch `claude/issue-17915-served-tier-gates-the-strip`. Attribution is stated here in prose because a REST edit of a PR body appends its own footer block: the first edit of this body left two, and this revision sends none so the appended one is the only one. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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