Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d7345c4
spec: retire ≤16.x retirement kits; strict unknown-key refusal names …
claude Oct 26, 2026
a17caaa
feat(spec,cli)!: retire the ≤16.x retirement kits; the strict unknown…
claude Oct 26, 2026
00e1eb7
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 26, 2026
485a377
test(metadata-protocol,lint): the stored-row and object-door pins rid…
claude Oct 26, 2026
87883f0
test(metadata-protocol,service-automation,objectql): the remaining pi…
claude Oct 26, 2026
65ebe0b
test(runtime,service-datasource): the security-collection and restore…
claude Oct 26, 2026
34e9ff1
docs(spec,cli): the 18.2.0 note leads with "Upgrading from protocol 1…
claude Oct 26, 2026
ba44823
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 26, 2026
34b0688
docs(upgrading): link the protocol upgrade guide by its address, not …
claude Oct 26, 2026
93505c9
fix(ui-core): ActionDef stops mirroring the aged-out `shortcut` and `…
claude Oct 26, 2026
53e1488
test(spec,ui): the console twins refuse the aged-out App/Dashboard ke…
claude Oct 26, 2026
6d80aba
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 26, 2026
3829225
fix(ui/types,cli): the aged-out-key refusal imports the spec's guide …
claude Oct 26, 2026
1331519
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 26, 2026
c61ede6
test(cli): the unreachable-archive fixture spells its postgres user a…
claude Oct 26, 2026
2718c3c
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 26, 2026
c35a0c1
fix(ui/types): the list view twin and three public-block arms refuse …
claude Oct 27, 2026
a07453b
test(ui/types): the list view drift guard names the aged-out refusals…
claude Oct 27, 2026
f4ae858
Merge branch 'main' into claude/project-thread-q3olv3
claude Oct 27, 2026
045298a
chore(sdui): re-record the core and types source digests after the me…
claude Oct 27, 2026
f7c0481
test(app-shell): the relay census declares the five aged-out list vie…
claude Oct 27, 2026
0b5c536
Merge remote-tracking branch 'origin/main' into issue-25767-r4
claude Oct 27, 2026
0a482d6
chore(spec): regenerate the merge's generated artifacts over main's s…
claude Oct 27, 2026
7127bb1
Merge remote-tracking branch 'origin/main' into issue-25767-r4
claude Oct 27, 2026
9a6ec3c
chore(spec): regenerate the merge's generated artifacts over main's s…
claude Oct 27, 2026
3054274
feat(spec)!: keep the migration chain and its floor of 16; only the r…
claude Oct 27, 2026
002710c
docs(spec): regenerate the reference pages for the re-pointed retirem…
claude Oct 27, 2026
76dad1c
fix(spec,cli): step 17 drops the ids of the 45 aged-out conversions; …
claude Oct 27, 2026
edc91b0
Merge remote-tracking branch 'origin/main' into issue-25767-r4
claude Oct 27, 2026
393c768
chore(spec): regenerate the merge's generated artifacts over main's s…
claude Oct 27, 2026
cd60de5
Merge remote-tracking branch 'origin/main' into issue-25767-r4
claude Oct 27, 2026
a0e848f
test(cli): the unreachable-archive fixture spells its postgres user a…
claude Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/25767-retire-16x-retirement-kits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@objectstack/spec': minor
'@objectstack/lint': patch
'@objectstack/metadata-core': patch
---

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

Clause-②: no (narrowing)
Metadata-protocol: narrowed

**BREAKING** for a source or a stored `sys_metadata` row still carrying a key whose ≤16.x kit aged out.

- **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.
- **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.
- **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.
- **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.
- **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`).
- **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.
- **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.
- **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`.

<!-- 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 -->
14 changes: 7 additions & 7 deletions .claude/skills/spec-property-retirement/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
注册中了 `ui/FormView:aria`,protocol 11 的 `flow.node.type` 注册中了任意
`.type`(#4658)。对每个常见叶名,保证早已失效。
*条目同时启动老化时钟(#5898):* 门 (c) 读同一张表来决定这个墓碑的
`authorable-surface/` 行何时可删(~两个 major),所以你把它写在哪个 major 下,时钟就从那个
release 数起。比这张表更老的退役是**未申报的、因此不可删的** ——
没有东西能诚实地给它们定日期(叶匹配曾把 `data/Index:type` 的日期从一个不相干
的 `flow.node.type` 定到 major 11,而 baseline 文件自己的 git 历史始于
17.0.0-rc.0)。要删那类行,先考证它真实的 major、补条目、在 PR 里写明。
`authorable-surface/` 行何时可删(一个 major —— ADR-0087 2026-10-26 修订,#25767;
此前 ~两个),时钟从你写它的那个 major 的 release 数起。更老的退役由第二个来源
定日期:上一个 major 的 tag 上已带 `[RETIRED]` 的行可删;tag 不可读时只按表判。
两个来源都不认的墓碑**不可删**(叶匹配曾把 `data/Index:type` 定到 major 11);
要删那类行,先考证真实 major、补条目、在 PR 里写明。
⚠ 永不加一行你定不了日期的:这里的估计会被之后每个门当成事实读。
- [ ] **`surface` 保持散文 —— 它不再被匹配。** 按作者写元数据的方式写
(`flow.nodes[].outputSchema`),那也是 upgrade guide 打印的。多键 conversion
Expand All @@ -208,8 +208,8 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
条目,它**一处也拦不住**:`applyConversionsToStoredItem`(钉死)、automation
engine 的 flow rehydration、`applyArtifactForwardConversions`。对*改名*它意味着
「没有 alias 窗口,故意的」;对**默认值翻转**,只有确知输入早于翻转的 seam 才可重
放,其余按 id 退订 `excludeConversionIds` —— `app-hidden-to-unpublished` 在 artifact
门即如此。
放,其余按 id 退订 `excludeConversionIds` —— `flow-decision-mode-inclusive-explicit` 在
artifact 门即如此(`app-hidden-to-unpublished` 曾是另一例,随 ≤16.x 套件在 #25767 退役)。
- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts.template`(`registry.ts` 由
`gen:migration-registry` 从它与 `entries/` 整文件生成并被 git 忽略,在 `registry.ts` 里手改不会
进提交);`conversion.toMajor` **必须等于**该步的 major。**18 步**:conversion 只进
Expand Down
2 changes: 0 additions & 2 deletions content/docs/references/ai/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ const result = AIModelConfigSchema.parse(data);
| **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. |
| **surface** | `Enum<'ask' \| 'build'>` | optional (default: `"ask"`) | Product surface this agent binds ('ask' \| 'build') — ADR-0063 §1 |
| **skills** | `string[]` | optional | Skill names to attach (Agent→Skill→Tool architecture) |
| **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. |
| **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. |
| **active** | `boolean` | optional (default: `true`) | |
| **access** | `string[]` | optional | Who can chat with this agent |
| **permissions** | `string[]` | optional | Required permission-set capabilities |
Expand Down
1 change: 0 additions & 1 deletion content/docs/references/ai/skill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const result = SkillSchema.parse(data);
| **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 |
| **instructions** | `string` | optional | LLM instructions when skill is active — also served as an MCP prompt |
| **tools** | `string[]` | ✅ | Tool names belonging to this skill (supports trailing wildcard, e.g. `action_*`) — bound by the cloud agent runtime only |
| **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. |
| **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 |
| **active** | `boolean` | optional (default: `true`) | Whether the skill is enabled |
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this skill. |
Expand Down
2 changes: 0 additions & 2 deletions content/docs/references/api/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ const result = AnalyticsEndpoint.parse(data);
| **limit** | `integer` | optional | Maximum number of rows to return, applied after `order` — a non-negative integer (`0` returns no rows) |
| **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 |
| **timezone** | `string` | optional | |
| **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? }`. |
| **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. |

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

Expand Down
Loading
Loading