Skip to content

Commit dbf7e04

Browse files
feat(spec)!: retirement kits whose conversion retired at or below 16.x age out; the strict unknown-key refusal names the protocol upgrade guide (#26599)
Fixes #25767 Supersedes #26138 Clause-②: no (narrowing) Metadata-protocol: narrowed Skills-docs: follow-up #26303 Release: 18.3 ## 维护者速读(草稿) - **改了什么:** 按 #25767 的裁决(6434038284):≤16.x 的退役套件(50 个 D2 conversion 和它们的 fixture、strict 容器里的 55 个墓碑、authorable-surface 上的 61 行 `[RETIRED]`)从代码里删掉。被删的键由 strict schema 当未知键拒绝,错误里多一句话,指向协议升级指南。迁移链不动:支持底线仍是 16,第 17 步和它的 159 个条目文件都在,`os migrate meta --from 16` 照常从 16 重放到 18。 - **为什么改:** 每个 major 都在累积墓碑和 conversion,每张 spec 卡都要为它们买单。升级者需要的处方放在升级指南和当时的 release notes 里就够了。链的底线是发版策略,要另行裁决,不随套件老化一起动(#26138 原先连链一起砍,已按裁决撤回)。 - **风险与回滚:** ① 仍写着 ≤16.x 键的源码不再拿到逐键处方,只拿到未知键拒绝和链接。② `sys_metadata` 里仍是这种形状的行,读取时不再被改写,要在 18.2 或更早版本上用 `os migrate meta --stored --apply` 规范化一次(裁决把这条一次性提示放进 18.2 的 release notes)。③ 第 17 步的 graduated 列表少了 45 个已删 conversion 的 id,所以 `--from 16` 不再替你改这 45 类写法;步本身和它的语义条目都还在。回滚就是 revert 本 PR,不写数据,没有不可逆动作。 - **先看:** ① `packages/spec/src/migrations/registry.ts.template` 第 17 步的 `conversionIds`(删掉 45 个 id 的那一段);② `docs/adr/0087-metadata-protocol-upgrade-contract.md` 末尾的 2026-10-26 addendum 和 D2 窗口那条修订;③ `packages/spec/scripts/build-schemas.ts` 的 proof 1(一个 major 的时钟和 release-tag 定日期)。 - **两分钟验证:** `cd packages/spec && pnpm exec vitest run src/migrations/ src/shared/retired-key-migrate-sentence.test.ts`,预期全部通过:链的完整性钉子(每个 id 都是真 conversion)、从底线 16 的重放,以及新旧两种处方句式。 - **需要你做的:** 以 `GOVERNED_APPROVERS` 身份批准本 PR(Tier H:`docs/adr/**` 原地修订)。 ## What changes Maintainer ruling 6434038284 on #25767 (2026-10-27, 「同意」) scopes the D2 = A ruling of 2026-10-25. It keeps kit aging and withdraws the chain cut, and the release target is 18.3. This PR is #26138's branch reworked to that scope. #26138 is closed as superseded (6434144688). **Kept from #26138 (the kit ages out):** - The 50 D2 conversions with `retiredAfter` at or below 16.x (5 at 15.0.0, 45 at 16.1.0) are removed, with their fixtures. - The 55 strict-container tombstones already `[RETIRED]` at `@objectstack/spec@17.0.0` are removed, along with 61 `[RETIRED]` lines of the authorable surface and their liveness rows. - The 58 strip-container tombstones of the same age stay, because deleting one would turn a refusal into a silent strip. The four inline-credential refusals are not retirements and are untouched. - The strict unknown-key refusal carries `RETIRED_KEY_UPGRADE_GUIDE_SENTENCE` (`shared/protocol-upgrade-guide.ts`) after its fix channels, keeping the #5955 order. - The deletion gate ages a tombstone one major. Proof 1 dates a deleted line by an exact `RETIRED_KEYS_BY_MAJOR` row or by the previous major's release tag. - The console's own refusals in `ui/packages/types` cover the keys it used to inherit as tombstones: the five ListView keys and the three flat public-block spellings. - The lint, metadata-core, runtime, metadata-protocol, objectql, services and `ui/**` test follow-ups are kept. **Restored to `origin/main` (the step stays):** - `packages/spec/src/migrations/entries/**`, including the 159 major-17 files, the README and the `conversionIds` links of the three protocol-18 entries. - `registry.ts.template` with `MIGRATION_SUPPORT_FLOOR = 16`, `chain.ts`, `migrations.test.ts` and `semantic-relevance.test.ts`. - `packages/cli/src/commands/migrate/meta.ts` and its twelve tests, and `docs/adr/0142`. - The floor prose in `docs/protocol-upgrade-guide.md` and `content/docs/upgrading.mdx`. The guide pointer gains one sentence saying where an aged-out kit's prescription lives. - ADR-0087's "permanent chain", "forever artifact" and 2026-08-01 amendments are withdrawn, and the D3 text stands. **Reworded:** - **ADR-0087:** the status line, the D2 window bullet, the 2026-07-31 stored-row note and the addendum now say the kit ages out and the step stays. - **The changeset:** `minor` for spec, `patch` for lint and metadata-core. The cli is no longer touched in published code. - **Retirement house sentences, which follow the conversion rather than the floor:** - An edit a surviving step-17 conversion still applies (the twelve `toMajor: 17`, `retiredAfter: 17.7.0` ones: the flow-node aliases and lifts, the mongo alias, the retry default, the sharing-rule `full` → `edit` rewrite, the header subtitle alias and the inline action params) goes back to main's "Run `os migrate meta --from 16` …" sentence. - An edit whose 16.x conversion left says "This edit left `os migrate meta` with the protocol-16.x conversions: apply it by hand, or run the tool on a release that still carries the conversion", followed by the guide link. - No sentence names a floor of 17. ## Acceptance notes 1. **Step 17's graduated list named the aged-out conversions.** The ruling's split reading checked that no semantic entry links a removed conversion, and that holds. Step 17's own `conversionIds` list in `registry.ts.template`, however, named the 45 conversions retired at 16.1.0, so restoring it verbatim failed the chain's integrity pin ("every step references only real conversion ids"). - The 45 ids leave that list, with a comment naming the ruling. The step, its 77 semantic entries and its retired-key and retired-def rows are unchanged. - `--from 16` still replays 16 → 18, but it no longer applies those 45 edits. Each one's tombstone or guide entry names the hand edit. - The ADR addendum and the changeset say so. 2. **Pins re-pointed by hand, because each replayed an aged-out conversion:** - The floor replay in `migrations.test.ts` now uses `sharing-rule-access-level-full-to-edit` and `flow-node-crud-object-alias`. - The cli cross-hop pairing pin in `meta.report-order.test.ts` now uses the sharing-rule conversion. - The `--from 16` shape in `migrate-meta-protocol-range.test.ts` is now a sharing rule. - `migrate-meta-write.test.ts` takes #26138's protocol-18 fixtures at `--from 16`. Its aged-out `compareTo` control now says the conversion left, not the floor. - The three repo-project pins on `RETIRED_KEYS_BY_MAJOR[17]` go back to main. 3. **The optional simplification is not taken.** The ruling allowed dropping the release-tag dating source if proof 1 could date every deleted line by its registry row. It cannot: 46 of the 61 deleted `[RETIRED]` lines predate `RETIRED_KEYS_BY_MAJOR`, and no row names them. The tag source and its four `SKILL.md` lines stay, at net zero lines (337 → 337). 4. **Stored rows.** A `sys_metadata` row carrying a spelling whose conversion aged out is no longer rewritten at read. Canonicalize it with `--stored --apply` on 18.2 or earlier. `app-hidden-to-unpublished` is among the removed conversions, so the artifact door's refusal of it by id is removed as moot. The 18.2 release-notes notice is the ruling's part 3 and is not in this diff: `content/docs/releases/` is release-owned. 5. **Residuals unchanged by this rework, as listed on the card:** - The guidance tables are not kit rows, and whether they age with the kits is still open. - `pendingDataMigrations` is moot again while the chain still crosses into 17. - #26303 (skills) is handled by its own seat under the ruling. ## Verification All readings are at head `393c768582`, which carries a merge of `origin/main` at `add806c5a6`. <details><summary>Suites (local, under the verify lock)</summary> - `pnpm --filter @objectstack/spec test`: 728 files and 21889 tests passed, 1 todo. - `pnpm --filter @objectstack/spec test:repo`: 80 files and 1295 tests passed. - `pnpm --filter @objectstack/cli exec vitest run --project unit`: 337 files and 4904 tests passed. The integration tier is declared to CI. - metadata-protocol: 21569 passed and 19 skipped. runtime: 7423 passed and 19 skipped. objectql: 8789 passed. service-automation: 2708 passed. service-datasource: 975 passed. These ran at the tree of `76dad1ceeb`. lint (7389 passed) and metadata-core (823 passed) ran at `002710c1dc`. The later commits touch none of their files. - `pnpm --filter @object-ui/types test`: 393 files and 10696 tests passed. - `cd ui && pnpm exec vitest run scripts/`: 177 files passed and 1 failed. The failure is `check-i18n-dead-keys` "keeps every key it makes reachable OUT of both tiers", a 15 s timeout on the real-tree scan under full-suite load, the same on three runs. It passes when its file runs alone (`check-i18n-dead-keys` plus `check-vi-mock-inherit`: 217 tests passed), and the diff touches no i18n source. - Type-check of types, react, plugin-list, plugin-view, components, core, app-shell and console: all exit 0. - `pnpm --filter @objectstack/dogfood test`, the full battery at the final head: 300 files passed, 3 skipped. 2494 tests passed, 18 skipped. Lock verdict `command-exit 0`. </details> <details><summary>Generated artifacts and gates</summary> - `pnpm --filter @objectstack/spec check:generated`: all 14 artifacts current. `node scripts/check-regen-pending.mjs`: exit 0. - `node scripts/gen-sdui-manifest-node.mjs` then `check-sdui-manifest`: `sdui.manifest.json` byte-identical (sha256 `1f6e1ccb925a…`, 106 components). - `ui` `check:spec-symbols`: exit 0. - The derived gate sweep (`dispatch-gates.mjs --commands`): 150 derived, 150 run, 0 NOT-MEASURED. 149 exit 0, and 1 exit 1, which is `pnpm --dir ui exec vitest run scripts/` with the timeout named above. Reconciled with `--ran`. - Merges of `origin/main` went through `scripts/pm/os-regen-merge.sh`. Each time the driver took main's side of a generated surface, the branch's own aged-out `[RETIRED]` lines were deleted again before `gen:schema`: three in `data.json`, one in `system.json` and five in `api.json`. </details> _Generated by [Claude Code](https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 03e898c commit dbf7e04

228 files changed

Lines changed: 4070 additions & 8848 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
'@objectstack/metadata-core': patch
5+
---
6+
7+
feat(spec)!: retirement kits whose conversion retired at or below protocol 16.x leave the tree; the strict unknown-key refusal names the protocol upgrade guide
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: narrowed
11+
12+
**BREAKING** for a source or a stored `sys_metadata` row still carrying a key whose ≤16.x kit aged out.
13+
14+
- **The rule (ADR-0087, amended 2026-10-26, #25767 ruling D2 = A as scoped by ruling 6434038284).** A retirement kit — the `retiredKey()` tombstone and its D2 conversion with fixtures — lives through the protocol major after the one it retired in and is removed when the next major is cut. At 18 that removes the kits whose conversion retired at or below 16.x; 19 repeats the move one major on. The kit ages out; the migration chain's step stays.
15+
- **Before.** Such a key met a `retiredKey()` tombstone: the parse refused it with the key's own prescription in the error, `os migrate meta --from 16` listed and applied the mechanical edit, and a stored row in that shape was rewritten at read by the ≤16.x conversions.
16+
- **Now.** Where the tombstone sat in a strict container, the key is gone from the shape, so a `strictObject` refuses it as an unknown key. The refusal carries one more sentence, after its fix channels and ahead of its history sentence (`RETIRED_KEY_UPGRADE_GUIDE_SENTENCE`, exported with `PROTOCOL_UPGRADE_GUIDE_URL` from `@objectstack/spec`): it says that keys retired before the previous major are no longer prescribed in the error and names the protocol upgrade guide, `https://objectstack.ai/docs/protocol-upgrade`, where the prescription lives; the CHANGELOG of the release that retired the key carries it too. A tombstone that stays (its container strips unknown keys) ends its error with a sentence that says the edit left `os migrate meta` with the 16.x conversions, to be applied by hand or on a release that still carries the conversion, and links the guide.
17+
- **The chain stays.** `MIGRATION_SUPPORT_FLOOR` stays 16; step 17 and its entry files stay, and `os migrate meta --from 16` still replays 16 → 18, listing every surviving conversion and semantic to-do. Step 17's graduated list drops the ids of the 45 conversions that aged out, so their edits are no longer applied for you.
18+
- **Stored rows.** A `sys_metadata` row still carrying such a spelling is no longer rewritten at read; canonicalize it once with `os migrate meta --stored --apply` on 18.2 or earlier, before moving to 18.3. `app-hidden-to-unpublished` is among the removed conversions, so a pre-split `hidden: true` app row is no longer rewritten to `_unpublished` by any seam, and the artifact door's refusal of it by id is removed as moot (`@objectstack/metadata-core`).
19+
- **What left.** 50 D2 conversions with `retiredAfter` at or below 16.x (5 at 15.0.0, 45 at 16.1.0) and their fixtures; 55 tombstones already `[RETIRED]` at `@objectstack/spec@17.0.0` whose container is strict; 61 `[RETIRED]` lines of the authorable surface and their liveness-ledger rows. 58 same-age tombstones whose container strips unknown keys stay, because deleting one would turn a refusal into a silent strip; the four inline-credential refusals are not retirements and are untouched.
20+
- **The deletion gate.** `check:authorable-surface` proof 1 ages a tombstone one major (was two). A deleted `[RETIRED]` line is dated by an exact `RETIRED_KEYS_BY_MAJOR` row or, new, by the previous major's release tag: a line already `[RETIRED]` on the surface at `@objectstack/spec@<current major − 1>.0.0` was retired before that cut. The tag is immutable, so neither a row a PR writes nor one it deletes moves the clock; where the tag cannot be read the gate dates by the registry alone and says so. No gate was added.
21+
- **Who moves.** No `examples/**`, `ui/` or `hotcrm` source in this repository writes a removed key (the `ui/` hits for the removed names are the console's own same-named props and type mirrors, not readers of the retired spec keys, per the liveness ledger's measurements). A consumer source still carrying one meets the strict refusal and the guide link at `os validate`.
22+
23+
<!-- adr-0087: not-required (no-migration-prescription) the diff registers no retirement: it removes the ≤16.x kits that aged out under the 2026-10-26 ruling (ADR-0087 D2 = A), and nothing here prescribes a rewrite — each removed key's prescription lives in the protocol upgrade guide and in the CHANGELOG of the release that retired it -->

‎.claude/skills/spec-property-retirement/SKILL.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
191191
注册中了 `ui/FormView:aria`,protocol 11 的 `flow.node.type` 注册中了任意
192192
`.type`(#4658)。对每个常见叶名,保证早已失效。
193193
*条目同时启动老化时钟(#5898):* 门 (c) 读同一张表来决定这个墓碑的
194-
`authorable-surface/` 行何时可删(~两个 major),所以你把它写在哪个 major 下,时钟就从那个
195-
release 数起。比这张表更老的退役是**未申报的、因此不可删的** ——
196-
没有东西能诚实地给它们定日期(叶匹配曾把 `data/Index:type` 的日期从一个不相干
197-
的 `flow.node.type` 定到 major 11,而 baseline 文件自己的 git 历史始于
198-
17.0.0-rc.0)。要删那类行,先考证它真实的 major、补条目、在 PR 里写明。
194+
`authorable-surface/` 行何时可删(一个 major —— ADR-0087 2026-10-26 修订,#25767;
195+
此前 ~两个),时钟从你写它的那个 major 的 release 数起。更老的退役由第二个来源
196+
定日期:上一个 major 的 tag 上已带 `[RETIRED]` 的行可删;tag 不可读时只按表判。
197+
两个来源都不认的墓碑**不可删**(叶匹配曾把 `data/Index:type` 定到 major 11);
198+
要删那类行,先考证真实 major、补条目、在 PR 里写明。
199199
⚠ 永不加一行你定不了日期的:这里的估计会被之后每个门当成事实读。
200200
- [ ] **`surface` 保持散文 —— 它不再被匹配。** 按作者写元数据的方式写
201201
(`flow.nodes[].outputSchema`),那也是 upgrade guide 打印的。多键 conversion
@@ -208,8 +208,8 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
208208
条目,它**一处也拦不住**:`applyConversionsToStoredItem`(钉死)、automation
209209
engine 的 flow rehydration、`applyArtifactForwardConversions`。对*改名*它意味着
210210
「没有 alias 窗口,故意的」;对**默认值翻转**,只有确知输入早于翻转的 seam 才可重
211-
放,其余按 id 退订 `excludeConversionIds` —— `app-hidden-to-unpublished` 在 artifact
212-
门即如此。
211+
放,其余按 id 退订 `excludeConversionIds` —— `flow-decision-mode-inclusive-explicit` 在
212+
artifact 门即如此(`app-hidden-to-unpublished` 曾是另一例,随 ≤16.x 套件在 #25767 退役)。
213213
- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts.template`(`registry.ts` 由
214214
`gen:migration-registry` 从它与 `entries/` 整文件生成并被 git 忽略,在 `registry.ts` 里手改不会
215215
进提交);`conversion.toMajor` **必须等于**该步的 major。**18 步**:conversion 只进

‎content/docs/references/ai/agent.mdx‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ const result = AIModelConfigSchema.parse(data);
5252
| **lifecycle** | `never` | optional | [REMOVED] `agent.lifecycle` was removed in @objectstack/spec 17.7.0 (ADR-0049 enforce-or-remove) — no runtime ever read it: no agent moved through a declared state and no transition was ever refused. Delete the key. A phase of a conversation is a skill with its own `instructions` and `tools`, selected by its `triggerConditions` (ADR-0064); multi-step process orchestration is a Flow (ADR-0019); a record's status transitions are a `state_machine` validation rule on the object (ADR-0020). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
5353
| **surface** | `Enum<'ask' \| 'build'>` | optional (default: `"ask"`) | Product surface this agent binds ('ask' \| 'build') — ADR-0063 §1 |
5454
| **skills** | `string[]` | optional | Skill names to attach (Agent→Skill→Tool architecture) |
55-
| **tools** | `never` | optional | [REMOVED] `agent.tools` was removed in @objectstack/spec 17 — use `skills`. An agent reaches exactly the tools its surface-compatible skills declare (ADR-0064), so move each reference into a skill: a platform tool by its registered name, or `action_<name>` for one of your own AI-exposed Actions. This is NOT a rename — there is no key the value moves to: the migration DELETES the key and emits a notice naming each tool that was listed, and you re-declare each one in a skill by hand. ADR-0064 itself still reads `Proposed` and is cloud-owned — that scopes its RUNTIME half (tool resolution, which lives in cloud `service-ai`), not this rejection: the authoring invariant binds you here, and ADR-0109 (Accepted — implemented) is the in-repo record that carries it. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
56-
| **knowledge** | `never` | optional | [REMOVED] `agent.knowledge` was removed in @objectstack/spec 17.0.0 (audit close-out) — declaring knowledge sources/indexes on an agent never scoped retrieval: the `search_knowledge` tool takes `sourceIds` from the LLM's tool-call arguments, not from the agent record. Delete the block. Restrict retrieval at the knowledge-service / source level (per-source permissions), and describe intended grounding in `instructions` so the model asks for the right sources. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
5755
| **active** | `boolean` | optional (default: `true`) | |
5856
| **access** | `string[]` | optional | Who can chat with this agent |
5957
| **permissions** | `string[]` | optional | Required permission-set capabilities |

‎content/docs/references/ai/skill.mdx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const result = SkillSchema.parse(data);
3939
| **surface** | `Enum<'ask' \| 'build' \| 'both'>` | optional (default: `"ask"`) | Agent surface this skill binds to ('ask' \| 'build' \| 'both') — ADR-0063 §3; read by the cloud agent runtime only |
4040
| **instructions** | `string` | optional | LLM instructions when skill is active — also served as an MCP prompt |
4141
| **tools** | `string[]` | ✅ | Tool names belonging to this skill (supports trailing wildcard, e.g. `action_*`) — bound by the cloud agent runtime only |
42-
| **triggerPhrases** | `never` | optional | [REMOVED] `skill.triggerPhrases` was removed in @objectstack/spec 17.0.0 (audit close-out) — phrases were never matched against the user's message; skill activation is `triggerConditions` (AND of context field/operator/value) intersected with the agent's `skills[]`, plus explicit /skill-name pinning. Delete the key. Put routing intent in `triggerConditions`; describe intent in `description`/`instructions` for the LLM. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
4342
| **triggerConditions** | `{ field: string; operator: Enum<'eq' \| 'neq' \| 'in' \| 'not_in' \| 'contains'>; value: string \| string[] }[]` | optional | Programmatic activation conditions — evaluated by the cloud agent runtime only |
4443
| **active** | `boolean` | optional (default: `true`) | Whether the skill is enabled |
4544
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this skill. |

‎content/docs/references/api/analytics.mdx‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,6 @@ const result = AnalyticsEndpoint.parse(data);
100100
| **limit** | `integer` | optional | Maximum number of rows to return, applied after `order` — a non-negative integer (`0` returns no rows) |
101101
| **offset** | `integer` | optional | Number of rows to skip before the first row returned, applied after `order` — a non-negative integer; an `offset` with no `limit` returns every row after it |
102102
| **timezone** | `string` | optional | |
103-
| **query** | `never` | optional | [REMOVED] `query` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0. The `{ cube, query: {...} }` envelope was the dialect of the retired degraded analytics shim — the real engine never understood it. Move the query.* fields to the body top level: `{ cube, measures, dimensions?, where?, timeDimensions?, order?, limit?, offset?, timezone? }`. |
104-
| **format** | `never` | optional | [REMOVED] `format` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0. It was never implemented — every response is the JSON envelope. Delete the key; for CSV/XLSX use the export surface instead. |
105103

106104
### Nested Shape: `AnalyticsQueryRequest.timeDimensions[number]`
107105

0 commit comments

Comments
 (0)