fix(spec): #4001 重测 —— 实测剩余站点清单,并修好测量它的那把尺 - #4852
Conversation
…r measuring it
Measurement step only — no schema changes posture in this commit. Produces the
real remaining-site list the 2026-08-03 ruling asked for before the remaining
batches are cut.
The site counter now reads the AST instead of matching source text. The textual
method was wrong in BOTH directions on seven files: it counted `z.object({ … })`
appearing inside JSDoc prose (ui/action.zod.ts declared 9 sites, has 8), and it
missed both the prettier-wrapped `z\n .object({` form (ui/chart.zod.ts 6 -> 7)
and `z.looseObject(` (data/field-value.zod.ts 1 -> 2). On ui/ the two errors
cancelled exactly, so a correct section total sat over two wrong rows.
The consequential case: automation/time-relative-trigger.zod.ts counted ZERO,
because its only site is written wrapped — and a zero-site file is deliberately
skipped by the coverage walk. An authorable schema sat outside the map while the
gate printed "no undeclared schema files". Not the data/driver/ blind spot
repeated: that walk was blind, this walk was fine and honoured a zero the
counter got wrong. Now classified.
check:strictness-ledger gains a remaining-strip-site map: per file, how many
object sites still silently discard unknown keys. That number is what batch
plans are scheduled against and nothing measured it before — which is why the
ruling was cut against `strictObject(` occurrence counts, an idiom that misses
every schema closed with the older `z.object(…).strict()` spelling and reads
automation/ as 0 strict when it has 8. Gated in both directions: a file with
strip sites must have a row, and a row whose file reaches zero strip sites
FAILS, so a closed file drops out of the worklist rather than outliving it.
Every new check verified to go red on a known input before being trusted
(campaign rule): the four remaining-strip failure modes were each induced and
observed, and the posture reads carry mutation controls in the test file.
Refs #4001, #4721
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
…ictness-remeasure
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
状态说明:本 PR 从未进入合并队列,已重跑失败 job跟进时发现 auto-merge 挂了约 100 分钟未动,而同期队列消化了 #4841 / #4870 / #4863 / #4872 / #4869 等多单 —— 排查后确认不是队列慢: 原因是时序:本 PR 的 CI 红在 10:1xZ,签名是 #4796 的 5s 超时族;而那条的修复 #4856( 已
Generated by Claude Code |
更正:重跑不够 ——
|
…) (objectstack-ai#4893) 2026-08-03 的 v17 协议变更派发里,有四类已经付出过代价的经验,现行 skill 里 没有或只写了半截。同日另一车道的 objectstack-ai#4885 已沉淀八条,本单只补它覆盖边界之外的。 pm-dispatch —— Operational notes 由四条扩到八条: - 5:rerun_failed_jobs 复用原 run 的提交/合并 ref,不重算。红的原因若是「基上 缺一个已合并的修复」,重跑无效,只有推新提交才拿得到新的合并 ref(objectstack-ai#4852 因此 在队列外空转 100 分钟)。与 rerun-safety-nightly.yml 无关,后者查的是测试污染。 - 6:读数纪律。cd X && cmd 短路会在错的仓里执行(跨仓一律 git -C); git grep -c | wc -l 数的是文件数不是命中数;裸名 grep 会被幸存家族当子串命中, 退役核验要带引号精确名、更硬的判据是查声明式而非提及。零命中必须用确定存在的 邻近词反查。 - 7:CI 红了先取完整日志归档。completeness check 绿只说明没有 worker 静默死掉; turbo 并发输出相邻不等于因果(test 的 dependsOn 只有 ^build,spec 无 pretest); 不要只看 tail。据错误结论开的 PR 要撤回 draft 并解绑 Fixes。 - 8:共享基础设施类修复按症状复查 main。duplicate-fix-guard.yml 只覆盖「同仓 + 同一个 Fixes #N」,objectstack-ai#4864 与 objectstack-ai#4856 挂在不同 issue 号下,门禁看不到,而后者先合的 60s 会被前者降回 30s。 另在 note 1 上补:「不在 main 上」是二义读数(兼容「排队中」与「没入队」);队列 分支 base sha 串成链,可读出排第几;转 draft 会同时掉 auto-merge 与队列成员资格。 step 7 之后新增「入队与落地」小节:merge=os-regen 的七条路径(含两条文档产物)、 四步同步协议、以及跟到 MERGED 而不是跟到入队为止。 spec-property-retirement: - 新增「四张 ratchet 的可见性按路线相反」—— 枚举值收窄不可见(objectstack-ai#4391),整 def 删除必须变化(objectstack-ai#4834:-12/-23/-5);拿错对照会双向判错。 - 修好第 2 节指向 plugin-runtime.zod.ts:243-248 的先例引用,该文件已被 objectstack-ai#4878 整体删除。 Fixes objectstack-ai#4892 Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9 Co-authored-by: Claude <noreply@anthropic.com>
…t accidental (objectstack-ai#4001) (objectstack-ai#4909) * fix(spec): make the bulk-action option item's openness deliberate, not accidental (objectstack-ai#4001) `BulkActionParamSchema`'s `options[]` entry was bare default-strip while the strictness ledger's prose called it and its parent "both deliberately open" — one intent, two postures, found by the objectstack-ai#4001 2026-08-03 re-measure (objectstack-ai#4852). Per the maintainer's verdict A the code moves to the prose: `.passthrough()` on the option item. Verified before writing, since the prose itself had just been proven able to drift — objectui's option TYPE is closed (`Array<{ label; value }>`, `packages/types/src/objectql.ts:271`), but the type is not what an authored option meets: `bulkParamToField` SPREADS every entry (`packages/plugin-grid/src/components/bulkParamToField.ts:131`) into the field metadata, whose vocabulary is `SelectOptionMetadata` (`packages/types/src/field-types.ts:288`) — `color` / `icon` / `disabled` / `visibleWhen` beyond the pair, and read (`packages/fields/src/index.tsx:1089`). So the strip was deleting widget config the renderer would have honoured. The new test states the openness is deliberate and names the evidence, so a future strict-ification sweep stops there instead of "fixing" it. Ledger: `ui/bulk-action.zod.ts` reaches zero strip sites, so its remaining-strip row is deleted and the header arithmetic reconciled (124 → 123 of 198). Both directions of that gate were driven red before the green was believed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(spec): keep the option shape readable after the passthrough (objectstack-ai#4001) `gen:docs` renders a passthrough object inside an array as `Record<string, any>[]`, so the reference page lost the `{ label, value }` shape the moment the entry opened — an author-facing surface reading "shapeless" for something that still has two required keys, which is the strip-era smell pointed the other way. The declared pair now lives in the key's own `.describe()`, which the generator renders verbatim, along with WHY the entry is open and which extra keys the widget actually reads. Regenerated `content/docs/references/ui/bulk-action.mdx` accordingly (the one artifact `check:generated` proved stale — not the set). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
…objectstack-ai#4974) * feat(spec)!: close nine authorable automation shapes against unknown keys (objectstack-ai#4001 batch 11) zod's default `.strip` discards an undeclared key and parses on. Batch 11 closes the nine remaining shapes in `automation/`'s main body, each rejection naming the surface, the key, and — where the word is recognisable — the canonical spelling. `flow.zod.ts`: the four OUTER shapes were closed earlier; their six nested authoring blocks were not, so the gate rejected `nodee:` at node level while `connectorConfig: { connectorID }` parsed clean and dispatched against an undefined connector id. Now strict: `FlowNode.connectorConfig` / `.position` / `.inputSchema` / `.waitEventConfig` / `.boundaryConfig`, `Flow.errorHandling`. Deliberately still open, now pinned in code AND tests so a later sweep stops rather than "finishing" the file: the node `config` slot (ADR-0018) and `FlowVersionHistorySchema` (emitted, not authored — its `definition` is `FlowSchema`, so the authored half is gated anyway). Every alias is a real in-repo spelling of the same knob on a neighbouring surface, taken from an AST census over the repo's own payloads, not guessed: `backoffMs` from `shared/retry-policy.zod.ts` (objectstack-ai#4661), `initialDelayMs` / `maxDelayMs` from connector `RetryConfig`, `attachedToRef` / `cancelActivity` from BPMN 2.0. Two cases get a prescription instead of a rename because a rename would be wrong: `inputSchema.optional` is the opposite polarity of `required`, and `errorHandling.maxAttempts` counts the first attempt while `maxRetries` does not — renaming it alone would quietly run one attempt fewer. `time-relative-trigger.zod.ts`: the schema objectstack-ai#4852 made visible (its only site was line-wrapped, the old counter read zero, and a zero-site file is skipped by the coverage walk). It sits under the open node `config` slot, so it is the only key gate there is — and it is `safeParse`d at BIND time, so `offsetDay` beside a valid mode used to bind a sweep with the author's narrowing discarded and report itself configured. `flow-function.zod.ts`: binds at authoring only (boot reads `normalizeFlowFunctionEntry`), stated in the code rather than implied. That is why it matters: the reader takes two keys and ignores the rest by construction, so a misspelled `effect` was dropped and then not looked for — the function ran and its writes were counted as none, keeping objectstack-ai#4354's broken-sweep query silent. `webhook.zod.ts`: the ledger's `(p)` resolved to the opposite of "spec-only" — three parse doors, one of them `bootstrapDeclaredWebhooks` at BOOT, which warns and SKIPS on failure. Hence the ADR-0010 envelope in the same commit: `applyProtection` stamps `_packageId`/`_provenance` on every type, so closing the shape without declaring them would have turned every package-shipped webhook into a skipped subscription after a redeploy. Strictness also rides `.extend()` onto the connector `WebhookConfigSchema` — verified against real zod and pinned in `connector.test.ts`. Ledger: `automation/` 67 → 58 strip; three rows reached zero and were deleted by the objectstack-ai#4852 reverse pin. Gate re-proved red in both directions before trusting it, and every new assertion was proved to depend on the tightening by neutering `strictObject` (19 red, restored byte-identical). Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(spec): regenerate reference pages for the webhook ADR-0010 envelope `gen:docs` only — the property tables for `automation/Webhook` and the `integration/WebhookConfig` extension gain the `protection` block and the seven `_lock*`/`_provenance`/`_package*` rows. Checked for the objectstack-ai#3746 trap: no page description or frontmatter moved, so no internal comment leaked onto a public reference page. Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * fix(spec): correct three history strings that named the wrong failure Self-review against the actual behaviour, before the claim shipped. The `connectorConfig` / `boundaryConfig` / `position` / `inputSchema` prose named a slip on a REQUIRED key (`connectorID`, `attachedToRef`) as the silent case. It never was: a required key spelled wrong then reads as MISSING, which zod has always rejected loudly. Nothing was silent about it. What `.strip` actually swallowed is the OPTIONAL half — the mapped `input` map (dispatched as `input ?? {}`: a successful connector call carrying nothing), BPMN's `cancelActivity: false` (so `interrupting` fell to its `true` default and a NON-interrupting event cancelled the host anyway), `optional: false` (so `required` fell to false and `validateNodeInputSchemas` had nothing to require), a canvas hint beside x/y. I.e. exactly the keys an author adds to CONSTRAIN behaviour, replaced by a permissive default. The corrected version is the stronger argument as well as the true one, and getting it wrong is the failure mode this campaign has already paid for four times (the withdrawn guidance prescriptions): rejection prose is behaviour, not documentation. Fixed in the schemas, the module comment, the tests and the ledger row; a test now pins the required-key case as loud so the distinction cannot quietly re-blur. Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
…as no-door (objectstack-ai#4001 批 14) (objectstack-ai#5042) Eleven strip sites measured per the objectstack-ai#4852 remeasure; the counts held exactly (1+1+2+2+3+2, confirmed against the ledger gate's own AST counter). Per-schema door measurement then split them nine / two. Closed with strictObject: ui/action.zod.ts ActionParamSchema.options[] ui/sharing.zod.ts SharingConfigSchema ui/report.zod.ts ReportSortSchema, JoinedReportBlockSchema ui/dataset.zod.ts DatasetDimensionSchema, DatasetMeasureSchema, .derived ui/dashboard.zod.ts DashboardWidgetSchema.compareTo (object arm), .layout Four of those are strict shells over strip children: the container was already strict and strictness does not recurse. An action param option carrying color/visibleWhen/icon/disabled parsed clean through getMetadataTypeSchema('action') and came back {label, value}. Reclassified as the fourth class (no door), NOT tightened: ui/notification.zod.ts NotificationActionSchema ui/sharing.zod.ts EmbedConfigSchema Both measured three ways with positive controls in the same run: no carrier key, unreachable in a 6860-node BFS from the 24 metadata-type roots + defineStack, zero parse outside their own tests. Injecting a synthetic carrier flipped both, so the verdict is a fact about the graph rather than a broken walker. sharing.zod.ts is the ledger's first file that splits across the class. action's option entry goes strict rather than following bulk-action's .passthrough() (objectstack-ai#4909): measured, not inherited. That def reaches the grid verbatim with no spec door and objectui's BulkActionParam declares an explicit index signature; this path has a door that already strips and lands in the CLOSED SelectOptionMetadata. Curation anchored on named sibling contracts, not edit distance: dataset on the Cube layer this module's header names (a Cube metric's `type` IS its aggregation), report's order key on SortNodeSchema and the widget's flat sortBy/sortOrder, layout on React-Grid-Layout. Known reach limit, measured: compareTo is a union, so its prescription is produced but not delivered (zodIssuesToFields maps only top-level issues; objectstack-ai#5014). The rejection is unaffected. Tests pin both halves separately. Out-of-scope findings filed unassigned: objectstack-ai#5013 (ReportSchema's `filter` alias prescribes `filters`, which it also rejects, plus 5 dead entries), objectstack-ai#5014, objectstack-ai#5015 (ADR-0049 verdict for the two no-door shapes), objectstack-ai#5016 (should action options carry the field-level per-option vocabulary). Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 Co-authored-by: Claude <noreply@anthropic.com>
…+ five i18n shapes as no-door (objectstack-ai#4001 批 16) (objectstack-ai#5069) Fifteen strip sites measured per the objectstack-ai#4852 remeasure; the counts held exactly (widget 9/9, i18n 6/6, confirmed against the ledger gate's own AST counter). Per-schema door measurement then split them one / fourteen. Closed with strictObject: ui/i18n.zod.ts AriaPropsSchema The highest-fan-out single site the ui/ wave has closed — carried as `aria:` on ~30 shapes under six metadata-type roots (ListView, Page, PageComponent, DashboardWidget, ChartConfig, Action, 20 SDUI component defs). Through the view root, `aria: { label, describedBy }` parsed clean and came back `aria: {}`: the accessible name existed in the source file and nowhere else. Those two spellings are objectui's own ARIA_KEY_ALIASES, i.e. what stored view metadata carries. Reclassified as no door, NOT tightened (14 sites): ui/widget.zod.ts all six shapes / nine sites ui/i18n.zod.ts I18nObject, PluralRule, NumberFormat, DateFormat, LocaleConfig Measured three ways with positive AND negative controls in the same run: no carrier key (nothing imports widget.zod but the barrel), unreachable in a 4766-node BFS from the 24 roots + defineStack, zero parse in all three repos (cloud checked by code search against a proven-working index). A synthetic carrier flips all of them, so the verdict is a fact about the graph. Curation anchored on named sibling contracts, not edit distance: the two legacy spellings on objectui's normalizer, and `ariaRole` on this shape's own inconsistent prefix (two of three keys carry `aria`, `role` does not). The four the distance fallback already reaches were measured first and left to it. `live` and `ariaLabelledBy` get a prescription rather than a rename — finding 7 discipline. `live` is real and rendered by objectui's ListView alone, which declares it via `AriaPropsSchema.extend({ live })`, so that surface keeps accepting it and now inherits this error map; promoting it into the shared shape would advertise aria-live on 29 renderers that do not implement it (objectstack-ai#5058). A `.strip()` was added to four files this batch does not otherwise touch: animation, dnd (x2), keyboard, touch build their shapes as `z.object({…}).merge(AriaPropsSchema.partial())`, and `.merge()` adopts the incoming posture — closing AriaProps would have silently closed all five of those no-door shapes with zod's generic message and no changeset. Pinned. Ledger finding 20: the campaign's own BFS reported widget.zod.ts as REACHABLE. Zod's `.describe()` returns a clone sharing the original `_zod.def`, so a bridge firing on any ONE shared property under a shared name links unrelated shapes via `name`/`label`. 2 keys of 20. The error is one-directional — it can only make a batch tighten something dead (objectstack-ai#4583). Corrected to whole-shape overlap in ui/door-reachability.testkit.ts, pinned as a regression test, filed as objectstack-ai#5056. Instruments proven red before trusting green: six sabotages, one per assertion class (walker, clone bridge, strictness, curation, finding-7 guidance, merge-strip), each reverted. Ledger: ui/ 118 strip of 198, authorable subtotal 82 — both recomputed from the surviving rows, never decremented by this batch's count. Refs objectstack-ai#4001. Filed out of scope: objectstack-ai#5055 (ADR-0049 for the 14 no-door sites), objectstack-ai#5056, objectstack-ai#5058, objectstack-ai#5059. Co-authored-by: Claude <noreply@anthropic.com>
Refs #4001
Refs #4721
按 2026-08-03 维护者裁决「必要的全收」执行注意第 1 条 —— 先重测,再切批。
本轮不改任何 schema 的 strictness 姿态。 产出是一份实测的剩余站点清单,供 PM 据实拆批。
一、先说结论:那把尺本身是坏的,而且两个方向同时坏
台账的站点计数一直是对源码做文本匹配(
z.object(/strictObject()。这次用一把独立的 AST 尺去对,七个文件不一致 —— 出现在这场战役专门为了「地图不许悄悄过期」而写的那个闸门里。ui/action.zod.ts声明 9,实为 8(第 9 个在一段 JSDoc 里);kernel/metadata-protection.zod.ts、shared/suggestions.zod.ts整个是注释z\n .object({,z\.object\(匹配不到 ——ui/chart.zod.ts漏 1、kernel/manifest.zod.ts漏 2z.looseObject(data/field-value.zod.ts声明 1,实为 2在
ui/上这两个错误恰好抵消(−1 和 +1),于是 section 总数校验因为第二个错误而通过,盖在两行错的数字上。最要命的一个:
automation/time-relative-trigger.zod.ts计数为 0 —— 它唯一的站点是折行写的,而零站点文件会被覆盖检查刻意跳过(「没东西可分类」)。于是一个作者手写的可授权 schema 落在地图外,闸门照样打印「no undeclared schema files」。修法是结构性的而非再往正则里补一种拼法:计数器改读 AST,不会被格式或注释骗,也不用每出一种新写法就学一次 —— 和第 9 条发现里信封探针改走结构遍历是同一个动作。
二、同一次遍历顺带给出姿态,这才是重测真正要的东西
台账过去只能回答「这个文件里有没有
.strict()」,永远回答不了「它还有几个站点是开的」。而后者正是拆批要用的数。裁决当时是按
strictObject(的出现次数排的期 —— 那个写法漏掉每一个用更早的z.object(…).strict()关掉的 schema:strictObject(数ui/automation/data/security/studio/三、新增「剩余 strip 站点」地图,并且双向上闸
check:strictness-ledger现在还守这张表:反向那一半更重要。能活过自己工作的清单,一定会活过:这个台账已经记过一次自己把「已发布的功能」列成待办,ADR-0010 欠债清单当初也是为同一个理由加的反向钉。
四、每一条绿色都先证过红
战役自己的规矩:信一个绿色读数之前,先让它在一个你确知存在的东西上变红。
ui/action9→8、ui/chart6→7、data/field-value1→2、automation/一个未声明文件),补完才转绿;strictObject(还原成z.object(必须读成 strip,给一个 strip 站点加.strict()必须读成 strict。验证
check:strictness-ledgerPASS(两行绿:67 文件 / 5 目录;51 个未关闭文件 / 325 strip 站点)check:generated8/8@objectstack/spec:294 文件 / 7369 用例通过,tsc --noEmit干净pnpm typecheck:122/122origin/main(4 个提交)后复验:merge 未触及packages/spec/src,实测数不变,闸门仍绿范围
只改测量脚本 / 测试 / 台账 + 一个 changeset。零 schema 改动,
content/docs/releases/未触碰。剩余清单、分类依据、拆批建议与 #4721 现状核实见 issue #4001 上的重测报告评论。
🤖 Generated with Claude Code
https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Generated by Claude Code