feat(spec)!: 退役五个「lint 永远无法告警」的键,并接通 doc.tags (#4509) - #4664
Merged
Conversation
…d connect doc.tags (#4509) Closes the "顺带的三个小清理候选" section of #4509 — the part left over after #4558 landed the four structural disconnects. What groups the five retirements is not the type they sit on but WHY they had to go out in a major rather than after a deprecation cycle: four of the five carry schema DEFAULTS, and a default materialises at parse time, so the liveness advisory lint cannot tell a value the author wrote from one the schema supplied. Marking them would have warned on every mapping and every selector in existence — which is why the ledger recorded `_authorWarnSkipped` instead of `authorWarn`. For a key in that state, removal is not the escalation after a warning; it is the only channel that ever reaches the author. With spec at 17.0.0-rc.1 and pre-mode still open, that channel closes at `changeset pre exit` and reopens in v18. Removed (strict deletion + `guidance` prescriptions, ledger rows deleted): mapping.extractQuery promised an export path no exporter implements mapping.errorPolicy error handling belongs to the import REQUEST mapping.batchSize the write path sizes its own batches app.contextSelectors[].includeAll app.contextSelectors[].placement `includeAll` is the one worth reading twice: not unread but deliberately DISOBEYED, and for a security reason. Context selectors are mandatory-scope, so an "All" row would clear a scope that exists to be scoped — on Studio's package selector that means listing the platform's own system/cloud kernel packages to a developer who scoped to their own package. STUDIO_APP shipped authoring `includeAll: true` against a renderer that ignored it; that authoring site goes with the key here. `batchSize` deliberately offers no rename. bulkActionDef/connector/sync/offline /seed-loader/NoSQL-cursor `batchSize` are all live and enforced, but each is a different key on a different type sizing its own path. "Removed" plus a familiar name one line away is exactly how a dead setting gets laundered into a live-looking one — the same trap datasource.retryPolicy had to defuse against hook/job retryPolicy (which spell the delay `backoffMs`) in #4583. A pin test asserts the message names them as DIFFERENT keys. Retired ALIAS spellings (query, onError, errorHandling, errorMode, batch, chunkSize, skipErrors, showall, location) route to the same prescriptions rather than suggesting a rename onto a key that is also gone. Connected, not removed — doc.tags: `BookGroup.include` has always accepted `{ tag }`, and it could never match a single doc in any stack. Not because the matcher was missing: `matchesInclude` compares `doc.tags`, the book route already forwards `tags: d.tags`, and `ResolverDoc` already declared `tags?: string[]` annotated "(P3d; absent today)". The gap was one line at the AUTHORING end — DocSchema is strict and had no `tags` key, so writing one was a parse error and every doc reached the resolver with tags undefined. ADR-0049 says enforcement wins when the feature exists; removing the variant would also have discarded working matcher code and left authors a bare union error carrying no prescription. ADR-0087: new conversion `mapping-inert-keys-removed` (scoped to the `mappings` collection deliberately — a stack-wide strip would delete an enforced batchSize from connector/sync/bulk-action/offline) plus an extension of `app-dead-authoring-keys-removed` to drill the contextSelectors array; both wired into the protocol-17 D3 chain step. `allValue` was re-verified as its ledger note required: still live (the shell reads it for auto-selection and query-param defaulting), but its describe() no longer calls it "the value emitted when All is selected" — an event that cannot occur and never could. Incidental, from confirming the area gates while working the selector keys: filterAppForUser walks only the top-level `navigation` tree and never reads `item.areas`, so area-level visible/requiredPermissions are FAIL-OPEN, not merely unread. Recorded accurately in the ledger and filed as #4651 rather than fixed here — inventing an authorization mechanism inside a retirement PR is exactly what #4583 declined to do for managed read-only. mapping joins datasource at zero dead keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…onnect-issues-6wz8ew # Conflicts: # docs/protocol-upgrade-guide.md # packages/spec/src/migrations/registry.ts
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 2, 2026 15:32
os-zhuang
pushed a commit
that referenced
this pull request
Aug 2, 2026
#4664 (spec key retirements + doc.tags) landed on main and touched the same five files. Resolution: - authorable-surface.json / spec-changes.json / docs/protocol-upgrade-guide.md REGENERATED from source (gen:schema / gen:spec-changes / gen:upgrade-guide), never hand-merged — hand-editing the authorable surface is forbidden (#4650). The regenerated surface differs from main by exactly this branch's delta: `automation/RetryPolicy:backoffMs` added, `:retryDelayMs` relabelled [RETIRED], and system/RetryPolicy gaining jitter / maxRetryDelayMs / the tombstone. - conversions/registry.ts auto-merged; verified 40 entries, zero duplicate ids, every declared conversion grouped, both `mappingInertKeysRemoved` (#4664) and `retryPolicyConverged` (#4661) present in the major-17 block. - migrations/registry.ts hand-resolved: both sides appended a paragraph to step17's `rationale` and an entry to `conversionIds`. Kept both. #4664's paragraph ended the string literal, so the concatenation was repaired and this branch's opener reworded ("Finally" -> "The same window") to avoid two "Finally"s in one rationale. Re-verified after the merge that exactly ONE conversion clause still ends in `.retryDelayMs` and it is `retry-policy-converged` — #4664 added five retired leaves (extractQuery / errorPolicy / batchSize / includeAll / placement), none of which collide with this cluster under the #4659 leaf-name match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
os-zhuang
pushed a commit
that referenced
this pull request
Aug 2, 2026
…y convergence #4664 retires `mapping.errorPolicy` in the same 17.0.0 window and its values included 'retry', so a reader could plausibly assume it migrates into a `retryPolicy` block. It does not — different type, inert enum, and its own prescription points at the import REQUEST's options. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
This was referenced Aug 2, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 3, 2026
… 的区分说明 (objectstack-ai#4653) (objectstack-ai#4679) PR objectstack-ai#4662 合并时,这段说明还在未提交状态(worktree 被清理时一并丢失), 故补一个 changeset-only 的跟进。无代码改动。 同窗口的 objectstack-ai#4664 退休了 `app.contextSelectors[].placement`,而它的退休说明 里写着「`location` 曾是 `placement` 的别名」。Studio 插件的面板贡献点恰好 也有一个 `location` 键(`studio/PanelContribution.location`),两者在不同 schema 上、取值域不同、互不相关 —— 但对着 v17 release notes 逐条读的作者 很容易把两件事连起来,以为 `contributes.panels[].location` 也要改。 changeset 里主动写清这个区分,挡掉误解;同时记下另外四个退休键与 objectstack-ai#4668 与 `activationEvents` 均无语义交叉。 Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 3, 2026
…i#4661, C8) (objectstack-ai#4670) * feat(spec)!: converge RetryPolicy onto one declaration (objectstack-ai#4661) `@objectstack/spec/automation` and `@objectstack/spec/system` both exported `RetryPolicySchema` / `RetryPolicy` resolving to DIFFERENT declarations, so the shape a consumer got depended only on the import path (the objectstack-ai#4411 trap). They were never two concepts: the `try_catch` node's `retry` region and `job.retryPolicy` both compute `delay = base * multiplier^(retry-1)`, and both executors implemented that identical formula. One declaration now lives in `shared/retry-policy.zod.ts`, re-exported by both entries, carrying the union of what the two sides could express. Because the published def key is derived from the entry namespace, both `automation/RetryPolicy` and `system/RetryPolicy` survive with an identical key set — so the convergence costs exactly ONE authorable key instead of eight. Authorable surface: `automation/RetryPolicy:retryDelayMs` is the single casualty, TOMBSTONED (`retiredKey`) rather than deleted because neither owning schema is `.strict()` — a plain removal would have Zod swallow the authored number and silently fall back to the 1000ms default. Defaults are the half no gate can see: the authorable-surface ratchet compares key sets, and a default is not a key. `job.retryPolicy` defaulted `maxRetries: 3` / `backoffMultiplier: 2` where automation defaulted 0 / 1. The merged declaration takes 0 / 1 (retry replays side effects, so it is opt-in), and the `retry-policy-converged` conversion writes the pre-17 numbers explicitly into every existing `job.retryPolicy` that omitted them — deployed stacks keep their exact behaviour; only a newly authored omission changes meaning. Filed separately as objectstack-ai#4666 (gates are blind to default/constraint changes). `job.retryPolicy` gains `maxRetryDelayMs` and `jitter`, both now enforced in `runWithPolicy` rather than merely declared (ADR-0049). Baseline: 22 -> 20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * docs(changeset): clarify mapping.errorPolicy is unrelated to the retry convergence objectstack-ai#4664 retires `mapping.errorPolicy` in the same 17.0.0 window and its values included 'retry', so a reader could plausibly assume it migrates into a `retryPolicy` block. It does not — different type, inert enum, and its own prescription points at the import REQUEST's options. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * fix(spec): regenerate spec-changes.json after the main merge 上一个合并提交用集合合并解决 spec-changes.json 的冲突,但该文件是 对象数组而非字符串数组,集合合并退化成「取变了的那一侧」,丢掉了 main 侧 objectstack-ai#3355 的 object-managed-by-system-to-system-data 条目, check:spec-changes 因此报 stale。 改为真的跑 gen:spec-changes(+ gen:upgrade-guide 确认无漂移)。 现三条条目齐备:retry-policy-converged、 object-managed-by-system-to-system-data、 job-retry-policy-constraints-tightened。 check:spec-changes / check:upgrade-guide / check:skill-docs 均绿。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-zhuang <support@objectstack.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
收尾 #4509 正文的「顺带的三个小清理候选」—— #4558 落地四条主线之后剩下的部分。
为什么这批必须赶在 v17
把这五个键归到一起的不是它们所在的类型,而是为什么它们必须走 major 而不能先弃用一轮:五个里有四个带 schema 默认值,默认值在 parse 时物化,所以活性 lint 分不清「作者写的值」和「schema 填的值」。给它们打
authorWarn会对现存的每一个 mapping、每一个 selector 都告警 —— 这就是账本里记的是_authorWarnSkipped而不是authorWarn的原因。对处于这种状态的键,移除不是告警之后的升级手段,而是唯一能触达作者的通道。spec 现在
17.0.0-rc.1、pre 模式仍开着,这个通道在changeset pre exit时关闭,下次开启是 v18。移除(strict 删除 +
guidance处方,账本行删除)mapping.extractQuerymapping.errorPolicymapping.batchSizeapp.contextSelectors[].includeAllapp.contextSelectors[].placement'topbar'什么也没放os migrate meta --from 16自动改写既有源码。includeAll值得看两遍它不是没人读,是被故意无视,而且理由是安全的。context selector 是 mandatory-scope 语义,一个 "All" 行会把一个「存在就是为了被限定」的表面解除限定 —— 对 Studio 的 package 选择器而言,那等于把平台自己的 system/cloud 内核包列给一个明明已经限定到自己包的开发者看。渲染器无论这个 flag 取什么值都从不渲染 All 行,所以
includeAll: false没加固任何东西,includeAll: true也没解锁任何东西。STUDIO_APP就这么写着includeAll: true,对着一个忽略它的渲染器 —— 这个 authoring 点在本 PR 里跟着键一起走。batchSize刻意不提供改名建议bulkActionDef.batchSize、connector.batchSize、sync.batchSize、offline.batchSize、seed-loader 的、NoSQL driver cursor 的 —— 全都是活的、被强制执行的,但每一个都是另一个类型上的另一个键、各自定各自的批。「removed」再加上一行之外一个眼熟的名字,正是死设置被洗成活设置的路径。和一个 issue 之前datasource.retryPolicy要防的hook/jobretryPolicy(延迟拼作backoffMs)是同一个陷阱。有 pin 测试钉住报错文案必须把它们说成 DIFFERENT 键。退役的 alias 拼法(
query、onError、errorHandling、errorMode、batch、chunkSize、skipErrors、showall、location)路由到同样的处方,而不是建议改名到一个同样已经没了的键。接通,不移除 ——
doc.tagsBookGroup.include一直接受{ tag },而它在任何 stack 里都匹配不中任何一个 doc。不是因为 matcher 缺失:matchesInclude一直在比对doc.tags——packages/spec/src/system/book.zod.ts:213tags: d.tags——packages/rest/src/rest-server.ts:3218ResolverDoc一直声明着tags?: string[],注释写着(P3d; absent today)缺口在授权端的一行:
DocSchema是.strict()且没有tags键,所以在 doc 上写tags:是解析错误,每个 doc 到达 resolver 时tags都是 undefined。ADR-0049 说功能存在时 enforce 胜出。移除变体的代价还包括:union 成员没有干净的墓碑(
retiredKey只覆盖 object 键),作者只会拿到一条不带处方的 union 报错,而且要丢掉一份能用的 matcher 代码。ADR-0087
mapping-inert-keys-removed—— 刻意限定到mappings集合:全局 strip 会把 connector / sync / bulk-action / offline 上被强制执行的batchSize一起删掉app-dead-authoring-keys-removed下钻contextSelectors数组(同 major 同类型,新开一条会撞 fixture 互斥契约),expectedNotices4 → 6step17.rationale已扩写连带核验:
allValue它的账本 note 明写着 "re-verify if includeAll is ever removed" —— 照做了。仍然
live(shell 真的读它,用于自动选中和 query 参数默认化),但它的.describe()不再把它叫做「All 被选中时发出的值」:那个事件不可能发生,也从来没可能过。顺带发现(已另行记录,未在此修)
核验 selector 键时确认了
filterAppForUser只走顶层navigation树,从不读item.areas(rest-server.ts:1823在navigation缺失时提前返回),客户端 area 切换器也照渲染所有 area。所以 area 级的visible/requiredPermissions是 fail-open 的闸门,不只是没人读 —— 一个「隐藏的」或「需要权限的」area 对所有人可见。账本里已如实记录,并另开 #4651 走 enforce-or-remove 决策。没有在这里顺手修:在一个退役 PR 里就地发明授权机制,正是 #4583 对 managed 只读闸门拒绝做的事(#4584)。
跨仓:objectui#3208 记录了下次抬 spec pin 时会断的两条 parity 断言(objectui 的 selector schema 按引用派生自 spec,漂移守卫会如期触发)。
验证
pnpm build71/71、pnpm typecheck122/122、pnpm lint干净pnpm test132/132(dogfood 单独复跑 453 passed / 3 skipped —— 首轮它拿到 SIGTERM 是我并发跑 typecheck 造成的资源争用,非本 PR 问题)liveness/empty-state/authorable-surface/docs/api-surface/spec-changes/upgrade-guide/skill-refs/skill-docs/skill-examples/variant-docs/strictness-ledger/generated,外加check:i18ncheck:liveness:mapping 死键 3 → 0(继 datasource 账本判定的 20 条死键至今无人处置:三个块整块无人读,其中 readOnly 让一个 shipped 示例的「只读副本」可写(ADR-0049 enforce-or-remove) #4583 的 datasource 之后第二个归零的类型),app 6 → 4,doc 新增tags行为 livebatchSize那条同时断言文案含 "relocating" 与connector.batchSize|sync.batchSize)、两条 selector 拒绝、alias 路由、allValue存活、book tag 端到端匹配bulk-action/sync/connector/offline/ seed-loader 的batchSize未被 conversion 误伤authorable-surface.json五行消失是 strict 删除的预期特征(闸门 (a) 的绊线),本 PR 刻意删除🤖 Generated with Claude Code
https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
Generated by Claude Code