diff --git a/.claude/skills/spec-property-retirement/SKILL.md b/.claude/skills/spec-property-retirement/SKILL.md index 7766292e004..7a7e30c3afb 100644 --- a/.claude/skills/spec-property-retirement/SKILL.md +++ b/.claude/skills/spec-property-retirement/SKILL.md @@ -210,10 +210,12 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都 「没有 alias 窗口,故意的」;对**默认值翻转**,只有确知输入早于翻转的 seam 才可重 放,其余按 id 退订 `excludeConversionIds` —— `app-hidden-to-unpublished` 在 artifact 门即如此。 -- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts`;`conversion.toMajor` **必须等于**该步的 - major。**18 步**:conversion 只进 `conversions/registry.ts` 的 `MAJOR_18_CONVERSIONS`,照其头注按 - 标识符排序插入,本步 `conversionIds` 由它派生;`rationale` 只加一个 `STEP18_RATIONALE` 片段, - 按其头注插在你 D3 semantic id 的排序位;尾部追加被两处头注点名的 merge 测试拒收。 +- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts.template`(`registry.ts` 由 + `gen:migration-registry` 从它与 `entries/` 整文件生成并被 git 忽略,在 `registry.ts` 里手改不会 + 进提交);`conversion.toMajor` **必须等于**该步的 major。**18 步**:conversion 只进 + `conversions/registry.ts` 的 `MAJOR_18_CONVERSIONS`,照其头注按标识符排序插入,本步 `conversionIds` + 由它派生;`rationale` 只加一个 `STEP18_RATIONALE` 片段,按其头注插在你 D3 semantic id 的排序 + 位;尾部追加被两处头注点名的 merge 测试拒收。 **更早的步**:id 加进 `MIGRATIONS_BY_MAJOR[N].conversionIds`,扩写该步 `rationale`。⚠ 没有东西 直接断言「每个 conversion 都接进了某一步」,拼错的 id 在 replay 时被**静默跳过**; chain-replay 测试抓得到它,只因为没接线的 fixture 永远到不了自己的 `after`。 @@ -257,7 +259,7 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都 无墓碑删键 → 该行**消失**,这是门 (a) 的绊线,所以同一个 PR 里有意删掉它)与 `json-schema.manifest/.json`。#5837 起两者都按 category 分片 —— 门 禁把整个目录读成一个集合,退役流程不变;变的只是那一行住在哪个文件。然后 - `gen:spec-changes`、`gen:upgrade-guide`、`gen:api-surface`、`gen:docs`。 + `gen:api-surface`、`gen:docs`;新条目跑 `gen:migration-registry`;D4 投影不提交,本地不重生成。 「改了 X → 重新生成 Y」的表见 AGENTS.md。 - [ ] **Forms** —— 从 `packages/spec/src/**/*.form.ts` 剪掉 `{ field: '' }` 输 入。未强制能力的表单输入是假合规的 UI 半边。原位留一行注释。 @@ -332,6 +334,4 @@ pnpm check:i18n && pnpm --filter @objectstack/spec test - **截断的 grep 漏掉编写者。** `| head -8` 藏掉了 `SKILL.md` 的 `defineSkill` 示 例;`examples/` 有三处 `template: true`,不是一处。先按上下文找文件,再进文件里 grep 键 —— 且把 `tsc` 和门禁当权威清扫器。 -- **只改 conversion 的 `summary` 也会过期。** 那个字符串被逐字抄进 - `spec-changes.json` 的 `to` 字段与 upgrade guide 的表行,所以纯散文改动也要 - `gen:spec-changes` + `gen:upgrade-guide`,和任何别的改动一样。 +- **只改 conversion 的 `summary` 不欠重生成。** 它逐字进两条 D4 投影,都不提交;PR 渲染其 diff。 diff --git a/AGENTS.md b/AGENTS.md index f0a599e768b..c4f0d0a61c3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -703,7 +703,7 @@ Root also exports: `defineStack`, `composeStacks`, `defineView`, `defineApp`, `d ### Touched `packages/spec`? Regenerate its artifacts BEFORE pushing -`packages/spec` has **eight** checked-in generated artifacts, each with its own CI +`packages/spec` has **six** checked-in generated artifacts, each with its own CI gate. All of them live in one job — `TypeScript Type Check` in `lint.yml`, which is required and has no paths filter, so no gate can go dormant on the PR that breaks it. That job runs its gates **sequentially**, so the first stale artifact masks every one @@ -714,7 +714,7 @@ behind it. Match the change to the gate and regenerate up front: | A `.describe()` / TSDoc on any schema | `check:docs` | `gen:schema && gen:docs` | | A public export (added / removed / renamed) | `check:api-surface` | `gen:api-surface` | | An authorable key on a metadata schema | `check:authorable-surface` | `gen:schema` | -| An ADR-0087 conversion / migration registry | `check:spec-changes`, `check:upgrade-guide` | `gen:spec-changes`, `gen:upgrade-guide` | +| An ADR-0087 conversion / migration registry entry | `check:spec-changes`, `check:upgrade-guide` — in memory, red only when the registries do not project | nothing to commit — `gen:migration-registry` (install, build and test run it too) writes the gitignored `registry.ts` | | A `SKILL.md` (frontmatter or body) | `check:skill-docs`, `check:skill-refs` | `gen:skill-docs`, `gen:skill-refs` | | The react-blocks contract | `check:react-blocks` | `gen:react-blocks` | @@ -758,8 +758,9 @@ Principles the wrapper encodes (its own output is the authority on detail): a name, with accepted cases in the shrink-only, hand-edited `dual-source-exports.baseline.json`. -Two generators have **no** gate at all — `gen:openapi` and `gen:sbom`. Nothing verifies -their output is current; the wrapper reports that each run rather than staying silent. +Three generators have **no** gate at all. Nothing verifies `gen:openapi` and `gen:sbom` +are current, and `gen:migration-registry` writes gitignored output that install, build and +test regenerate; the wrapper reports all three each run rather than staying silent. --- ## Context Routing — apply the right role per path diff --git a/docs/adr/0087-metadata-protocol-upgrade-contract.md b/docs/adr/0087-metadata-protocol-upgrade-contract.md index 5aa16f2f7bc..12c6ab7e3b2 100644 --- a/docs/adr/0087-metadata-protocol-upgrade-contract.md +++ b/docs/adr/0087-metadata-protocol-upgrade-contract.md @@ -1,6 +1,6 @@ # ADR-0087: Metadata protocol upgrades for AI consumers — conversion over notification, executable migrations, machine-verifiable upgrades -**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum) · **Amended** (2026-09-30, #20390 ruling A — the artifact-ingestion window decides per entry by each retired conversion's `retiredAfter`; see the amendment note under the 2026-09-13 addendum's window bullet) · **Amended** (2026-10-09, #22449 ruling B′ — the two D4 projections, the per-major section of `spec-changes.json` and the protocol upgrade guide, are generated at publish rather than committed; see the amendment note under D4, which says what has landed and which cards carry the rest, and the struck clauses in the P2 phase-status bullet) +**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum) · **Amended** (2026-09-30, #20390 ruling A — the artifact-ingestion window decides per entry by each retired conversion's `retiredAfter`; see the amendment note under the 2026-09-13 addendum's window bullet) · **Amended** (2026-10-09, #22449 ruling B′ — the two D4 projections, the per-major section of `spec-changes.json` and the protocol upgrade guide, are generated at publish rather than committed; see the amendment note under D4, which says what is generated where and where each part landed, and the struck clauses in the P2 phase-status bullet) **Deciders**: ObjectStack Protocol Architects **Builds on**: [ADR-0059](./0059-third-party-backward-compatibility-gates.md) (layered backward-compat gates — this ADR is its consumer-facing sequel), [ADR-0078](./0078-no-silently-inert-metadata.md) (no declarable-but-unenforced metadata — the un-checked `engines.protocol` is exactly this class), [ADR-0025](./0025-plugin-package-distribution.md) (§3.2 `engines.protocol` / `engines.platform` compatibility ranges, §3.10 #3 protocol-first check order), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (the authoring population this ADR designs for), [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove), [ADR-0054](./0054-runtime-proof-for-authorable-surface.md) (prove-it-runs), AGENTS.md Prime Directive #12 (contract-first, no consumer-side dialect fallbacks — §"Why the conversion layer does not violate PD #12" draws the line) **Consumers**: `@objectstack/spec` (protocol version constant, conversion layer, deprecation/change registries), `@objectstack/cli` (`validate`, `doctor`, `migrate meta`), the runtime metadata loader (handshake + conversion), `@objectstack/mcp` (the AI-native change/migration surface), `@objectstack/create-objectstack`, the Release workflow, and every third-party consumer — whose maintainer is assumed to be an **AI agent** @@ -205,7 +205,9 @@ chain** — the database-migration model applied to metadata source files. > **Amended 2026-08-06 (#5837), paths only.** `api-surface.json` became the directory > `api-surface/` (one shard per entry point) and ships in the npm artifact as such; the > release diff reads whichever layout the previously published tarball carried. `spec-changes.json` -> is deliberately unsharded — it is keyed by version, which has never been a conflict surface. +> was left deliberately unsharded — it is keyed by version, which was never a conflict surface. +> That call was about merging its committed copy, which #22485 deleted (PR #22638): nothing +> merges it any more. > **Amended 2026-10-09 (#22449, ruling B′) — where the two projections live.** Ruling > `6078203801` on #22449, letter B′, which the maintainer answered 「同意」 at @@ -255,20 +257,23 @@ chain** — the database-migration model applied to metadata source files. > artifact when it is non-empty. That step is red only when a side cannot be > generated, never because of the size of a diff. > -> **The ruled end state, and the cards that still carry it.** Not landed when this -> note was written, and stated here as ruled rather than as done: +> **The ruled end state, landed** (trued up 2026-10-11). Not landed when this note was +> first written; both remaining cards have landed since, each deleting one committed copy +> and its `merge=os-regen` route in `.gitattributes`: > -> - condition (2), the guide's public address and the pointer stub at -> `docs/protocol-upgrade-guide.md` — #22483; -> - the deletion of the committed copies (the per-major projection in -> `packages/spec/spec-changes.json`, the guide body at -> `docs/protocol-upgrade-guide.md`) and of their two `merge=os-regen` routes in -> `.gitattributes` — #22485, which lands last. +> - condition (2) — #22483, landed as `514bf3c101` (PR #22556). The guide's public +> address is the docs site, one page per protocol major, generated at docs build into +> the gitignored `content/docs/protocol-upgrade/`. `docs/protocol-upgrade-guide.md` +> stays at its path as a hand-written pointer stub naming that address; the guide body +> it used to hold left git with its route; +> - the committed `packages/spec/spec-changes.json` and its route — #22485, landed last +> as `46064dfa03` (PR #22638). > -> Until #22485 lands the committed copies stay in the tree, and the generators' -> no-flag mode (`gen:spec-changes`, `gen:upgrade-guide`) still writes them, but -> `check:spec-changes` and `check:upgrade-guide` no longer compare them: they can go -> stale, and the copies that ship are the record. Condition (3) is cloud's half and +> No committed copy of either projection remains. `packages/spec/spec-changes.json` and +> `packages/spec/protocol-upgrade-guide.md` are gitignored paths the publish lane writes +> for `files[]` to ship, and the generators' no-flag mode writes only untracked output +> (`gen:spec-changes` that `spec-changes.json`, `gen:upgrade-guide` the docs pages), so a +> registry entry's pull request regenerates neither. Condition (3) is cloud's half and > lives in that repository (objectstack-ai/cloud#2750); this record governs the > framework half. > @@ -281,7 +286,7 @@ chain** — the database-migration model applied to metadata source files. > generators the publish lane runs, and the registry entries those projections are > computed from stay committed, reviewable source. What leaves git is the committed > aggregate and the re-sync laps it cost. The 2026-08-06 note above ("deliberately -> unsharded") is about that committed copy, which #22485 removes. +> unsharded") is about that committed copy, which #22485 removed. - The Release workflow diffs the current `api-surface.json` against the previously published one (reusing the ADR-0059 §3 gate artifact instead of discarding it), @@ -428,18 +433,19 @@ scaling framework-side execution when the consumers can execute themselves. **amended 2026-10-09** — it is generated from the registries at publish, as `protocol-upgrade-guide.md` inside the npm artifact and on the GitHub Release, and in memory, with its diff rendered, at pull request; - `docs/protocol-upgrade-guide.md` is ruled to become a pointer stub naming the - guide's public address, which #22483 carries. + `docs/protocol-upgrade-guide.md` became a pointer stub naming the guide's + public address, the docs site's per-major pages (#22483, PR #22556). > **Amended (2026-10-09) — the projections are generated at publish, not > committed.** Provenance: ruling `6078203801` on #22449 (letter B′; maintainer > 「同意」), which supersedes the 2026-08-10 #6957 ruling's rejection of option B > and keeps its reason, the review diff, as its condition (1); the generation > half landed as `5b12503c31` (PR #22533, card #22482). The struck clauses - > describe committed copies drift-checked in CI. Those copies stay in the tree - > until #22485 deletes them, and `check:spec-changes` / `check:upgrade-guide` - > no longer compare them. The amendment note under D4 is the full record: - > what is generated where, and what #22483 and #22485 still carry. + > describe committed copies drift-checked in CI. Those copies are gone — the + > guide's at #22483 (PR #22556), `spec-changes.json`'s at #22485 (PR #22638) — + > and `check:spec-changes` / `check:upgrade-guide` generate in memory and + > compare nothing. The amendment note under D4 is the full record: what is + > generated where, and where each part landed. - **P3 — MCP tools + RC discipline (D6): deferred, evidence-gated** (as the ADR Boundaries intended): built when external-consumer demand justifies the operational surface. Nothing else in this ADR depends on it.