Skip to content

Commit 7f4cbc9

Browse files
docs(skills): an email-template override names a locale its base ships; a new locale carries no overrides (#28814)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiHXKqxUqDPJKZyNHP7BoqTb)_ Fixes #28778 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:两处文档各补一条规则——邮件模板按「名称 + 语言(locale)」算一个条目,所以 `BASE.custom` 里带 `overrides` 的覆盖只能指向基础包确实发过的语言;基础包没发的语言直接新增、不写 `overrides`。改的是发布技能包 `skills/objectstack-platform/SKILL.md` 的 ADR-0146 定制段,和 `content/docs/concepts/metadata-lifecycle.mdx` 的 ADR-0146 节。 - **为什么改**:PR #28777 落地后,写入闸门已经按语言判断(基础包只发 `en-US` 却声明覆盖 `fr-FR` ⇒ `403 NOT_OVERRIDABLE`),而技能和文档只说「同名 + `overrides` 即覆盖」,对语言只字未提,照着写的 AI 作者会被拒。 - **风险与回滚**:极低,纯文档;无代码、无 schema、无行为变化。技能文件有 token 棘轮(上限 6012,改前余量 0):新句子靠把「尚未放行的八个类型」改为指向 `OVERRIDABLE_METADATA_TYPES`、删四句开场白付账,改后 6005,未动任何规则、未抬上限。回滚即 revert。 - **先看**:① `skills/objectstack-platform/SKILL.md` 第 131 行的定制段(新句子 + 类型列表的收缩);② `content/docs/concepts/metadata-lifecycle.mdx` 第 117–119 行与新增的「A new locale is not an override」条目;③ `.changeset/28778-email-template-override-locale.md`(`@objectstack/skills` patch)。 - **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs`,预期输出含 `skills/objectstack-platform/SKILL.md is 6005 tokens (ceiling 6012; headroom 7)` 且结尾 `✓ … 57 authored bundle file(s) within their ceilings`。 - **需要你做的**:`skills/**` 为 Tier H 受管面——由授权审批人在本 PR 上给出 APPROVED review,之后席位落地;PR 保持 draft、不请审、不指派。 ## What changes **The rule, taken from the gate PR #28777 landed, not from the card.** An email template's identity is `(name, locale)`: `ITEM_KEY_DISCRIMINATORS` (`packages/metadata-core/src/item-key-discriminators.ts:68-70`) holds exactly one entry, `email_template: { field: 'locale', canonical: 'en-US' }`, and `itemDiscriminator` (`:96-100`) keys a body that declares no locale as the canonical member. The override write gate reads that table through `overrideWriteMemberOf` (`packages/metadata-protocol/src/protocol-packaged-base-refusal.ts:280-282`) and `overrideWriteIdentityOf` (`:299-302`, name joined with member), and `judgeOverrideWrite` refuses `declared_package_ships_no_such_item` when no closure package ships the base at that member (`:448-455`, the sentence naming the locale: "ships no email_template named 'X' with locale 'fr-FR'"), thrown as `403 NOT_OVERRIDABLE` (`:535-542`). A locale the base does not ship, saved in the pair WITHOUT `overrides`, collides with no base member and is admitted as the pair's own member (pinned: `packages/qa/dogfood/test/email-template-override-unshipped-locale.dogfood.test.ts:125`; CLI `packages/cli/src/utils/override-declarations.test.ts:161`). - **`skills/objectstack-platform/SKILL.md`** (ADR-0146 customization paragraph, now L131): one sentence after the honoured-types sentence — *An email template is one item per (name, locale): an override names a locale the base ships; a locale the base does not ship is added without `overrides`.* - **`content/docs/concepts/metadata-lifecycle.mdx`** (ADR-0146 section): the "bears a base item's name" sentence now reads "— for an `email_template`, one item per `(name, locale)`, the name **and** a locale the base ships —", and a new bullet **A new locale is not an override** says the pair's own locale carries no `overrides`, is served under `hotcrm.custom`, and that declaring one there is refused (`403 NOT_OVERRIDABLE` at the save, publish and install doors, a row from `os validate`). - **`.changeset/28778-email-template-override-locale.md`**: `@objectstack/skills` `patch` (the catalog ships in that package's `dist`), `Clause-②: no`, `Metadata-protocol: none`. **Paying for the sentence (token ratchet, `skills/objectstack-platform/SKILL.md`, ceiling 6012, headroom on `main` 0 tokens / 3 bytes).** Nothing paid with a re-wrap; no rule dropped. What moved: 1. Same paragraph: the eight not-yet-honoured types (`page, app, dataset, doc, book, mapping, flow and action`) are no longer enumerated; the clause reads "the rest of `OVERRIDABLE_METADATA_TYPES` wait for their doors". The honoured five stay named; the docs page keeps the full list. 2. "Undeclared keys are handled per surface, and the two postures are worth keeping straight:" → "…, in two postures:". 3. "Every stack needs a `manifest` to identify itself in the ecosystem:" → "Every stack needs a `manifest`:". 4. The Runtime Boot Sequence intro line "Understanding how ObjectStack starts helps debug and customize:" removed (the heading carries it). 5. The driver table intro "Pick based on your environment:" removed (the table's "Best For" column carries it). **Skills readings (both, as the published-catalog rule requires; lines by `wc -l`, tokens by the ratchet's `ceil((bytes − stamp line) / 4)`):** | Reading | Before (`origin/main` c064d3a) | After (7390bef) | |:--|:--|:--| | `skills/objectstack-platform/SKILL.md` | 462 lines · 6012 tokens (ceiling 6012, headroom 0) | 459 lines · 6005 tokens (headroom 7) | | Whole package (every `skills/*/SKILL.md`) | 4561 lines · 56274 tokens | 4558 lines · 56267 tokens | The ratchet's authored-bundle total (57 files) reads 181895 / 190761 after; only this file moved (−7). ## Tests No code changes; no test added or moved. The gates below are the verification. ## Acceptance notes - **Labels:** the dispatch names none and `skip-changeset` does not apply (`@objectstack/skills` publishes `skills/**`), so `pr_create` carries no labels and no assignees. - **Local-only families** (`check:dev-prereqs`, `check:skill-frame-freshness`) are not derived for this diff (no spec build or four-axis frame file touched) and were not run. - Nothing out of scope was found; nothing filed. ## Verification All readings at head `7390bef4d6` after `pnpm install --frozen-lockfile` and a full `pnpm build --concurrency=2` under `os-verify-lock.sh` (VERDICT `command-exit 0`, held 1011 s). `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 71 commands for this diff (3 paths, 30 changed lines). Every one was run locally with its exit code captured before any pipe: **71 / 71 exit 0**, reconciled with `--ran`. <details><summary>Families the diff touches, with their verdict lines</summary> - `node scripts/check-skills-token-ratchet.mjs` → exit 0 — `✓ check-skills-token-ratchet: skills/objectstack-platform/SKILL.md is 6005 tokens (ceiling 6012; headroom 7)` · `57 authored bundle file(s) within their ceilings`. - `pnpm check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:skill-top-level-keys`, `check:skill-compatibility` → exit 0 each. - `pnpm --filter @objectstack/spec run check:skill-docs`, `check:skill-examples`, `check:docs`, `check:variant-docs`, `check:yaml-examples` → exit 0 each (after the build). - `pnpm check:doc-anchors`, `check:doc-authoring`, `check:overlay-whitelist-table`, `check:docs-single-h1`, `check:doc-frontmatter`, `check:docs-spec-enumerations`, `check:docs-transcript-drift`, `check:docs-redirects`, `check:docs-audit-scope` → exit 0 each. - `node scripts/check-empty-changeset.mjs --base origin/main`, `check-changeset-no-major.mjs --base origin/main`, `check-adr-0087-registration.mjs --base origin/main`, `pnpm check:changeset-gate-self-tests`, `check:pm-changeset-deadline-census` → exit 0 each. - `pnpm check:nul-bytes`, `check:pm-skill-id-lint`, `check:role-word` → exit 0 each. - The remaining derived commands (whole-tree and checker self-tests) → exit 0 each. </details> Not measured locally (CI's own): the type-check lanes, `Build Docs`, `Build Core`, and the `main`-push batteries the derivation lists; the PR's check-runs carry them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 56bd596 commit 7f4cbc9

3 files changed

Lines changed: 25 additions & 11 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-platform says an email-template override names a locale its base ships; a new locale carries no `overrides`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The ADR-0146 customization paragraph taught that a same-named item in the paired `.custom` package with `overrides` replaces the base's item, and listed `email_template` among the honoured types, but said nothing about the locale. An email template is one item per `(name, locale)`, and the override write gate refuses a declared override of a locale the base does not ship (`declared_package_ships_no_such_item`), so an author following the skill could declare `overrides` on a locale the base never shipped and be refused.
11+
- **Now.** One sentence in that paragraph: an email template is one item per (name, locale), an override names a locale the base ships, and a locale the base does not ship is added without `overrides`. The file stays at its token ceiling: the sentence is paid for by naming the not-yet-honoured types as the rest of `OVERRIDABLE_METADATA_TYPES` instead of enumerating them, and by dropping four filler intro clauses. No rule is removed, no frontmatter, schema or behaviour changes.

‎content/docs/concepts/metadata-lifecycle.mdx‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ later ships. See [ADR-0070](https://github.com/objectstack-ai/objectstack/blob/m
115115

116116
A customer's change to an installed base package — HotCRM, id `hotcrm` — is an item of
117117
the base's paired writable package **`hotcrm.custom`**, which depends on the base; the
118-
base's own items stay read-only. An item of the pair that bears a base item's name and
118+
base's own items stay read-only. An item of the pair that bears a base item's name — for an
119+
`email_template`, one item per `(name, locale)`, the name **and** a locale the base ships — and
119120
declares in its body `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` (the base's
120121
`manifest.id` and `manifest.version`; `basedOn` is recorded and never judged; schema:
121122
[Override Declaration](/docs/references/kernel/override-declaration)) replaces the base's
@@ -127,10 +128,15 @@ document **whole** for every read that serves the base's name
127128
override, `effective` = the override), the list scoped to the base and the
128129
environment-wide list all serve the override, stamped `_packageId: 'hotcrm'`. The
129130
declaration counts only from the pair — the stored row's own `package_id` must be
130-
`hotcrm.custom` — and today only for a `view`, `dashboard`, `report`, `email_template`
131-
or `translation`: the rest of `OVERRIDABLE_METADATA_TYPES` (`@objectstack/spec/kernel`)
132-
— `page`, `app`, `dataset`, `doc`, `book`, `mapping`, `flow` and `action` — waits for
131+
`hotcrm.custom` — and today only for a `view`, `dashboard`, `report`, `email_template`,
132+
`translation` or `flow`: the rest of `OVERRIDABLE_METADATA_TYPES` (`@objectstack/spec/kernel`)
133+
— `page`, `app`, `dataset`, `doc`, `book`, `mapping` and `action` — waits for
133134
its doors and is not served yet. A declaration from any other package is ignored.
135+
- **A new locale is not an override.** An `email_template` the pair saves at a locale the
136+
base does not ship is the pair's own member, served under `hotcrm.custom`, and carries no
137+
`overrides`; declaring one there is refused (`403 NOT_OVERRIDABLE` at the save, publish
138+
and install doors, a row from `os validate`, the sentence naming the locale), because the
139+
base ships no such member to replace.
134140
- **The environment-wide list** serves one item per name that exactly one code package
135141
ships. A dependent package's same-named item is not listed beside it and stays under
136142
its own `?package=` address. A package-unbound row — the overlay row of the table

‎skills/objectstack-platform/SKILL.md‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ ownerless row into a queue — parked, `round_robin` or `least_loaded` — or to
5555
user. Both need the ownership-queue service `@objectstack/plugin-sharing` registers
5656
(`sharing` in `requires`): a stack declaring either without it is refused at boot.
5757

58-
Undeclared keys are handled per surface, and the two postures are worth
59-
keeping straight:
58+
Undeclared keys are handled per surface, in two postures:
6059

6160
- **Refused** — `defineStack()`'s top level, each `objects[]` entry
6261
(`ObjectSchema`), and each field (`FieldSchema`). The parse throws, naming
@@ -108,7 +107,7 @@ OS_ARTIFACT_PATH=./dist/objectstack.json objectstack dev
108107

109108
## Manifest Reference
110109

111-
Every stack needs a `manifest` to identify itself in the ecosystem:
110+
Every stack needs a `manifest`:
112111

113112
```typescript
114113
manifest: {
@@ -131,7 +130,7 @@ to a new protocol major — never to silence a mismatch.
131130

132131
**`manifest.namespace` (ADR-0048):** Optional, but **enforced once set**. When a package declares `manifest.namespace: 'crm'`, every `object.name` must start with `crm_` or `defineStack` errors (`validateNamespacePrefix` in `@objectstack/spec`); the legacy `<ns>__<short>` double-underscore form is rejected, and `sys_`-prefixed names are platform-reserved and exempt. The namespace is also a package-ownership key — installing two packages that both claim `crm` fails with `NamespaceConflictError` (downgrade to a warning with `OS_METADATA_COLLISION=warn`). `os lint` additionally emits a non-fatal `naming/namespace-prefix` warning for bare-named UI/automation items (app, page, dashboard, flow, action, report, dataset) when a namespace is set.
133132

134-
**Customizing an installed package (ADR-0146):** Customize an installed base `hotcrm` in its paired writable package `hotcrm.custom`, which depends on it: a same-named item there with `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` (its `manifest.id`/`manifest.version`) replaces the base's document whole. The read honours it only from the pair (the row's `package_id`), today only for a view, dashboard, report, email_template or translation (page, app, dataset, doc, book, mapping, flow and action wait for their doors), never judges `basedOn` and ignores any other package's declaration. Then no `?package`, `?package=hotcrm`, the layered read and both lists serve it; the env-wide list holds one item per name one code package ships (a dependent's stays under its own `?package=`); a package-unbound Setup row still wins over the base's until D11. An undeclared pair row or a dependent's row is an orphan in the layered read's `overlay`, `effective` = the base's. An override may narrow a public form's anonymous intake, never re-open one any honoured body withdrew. `GET /packages/hotcrm.custom/export` carries it with `overrides`; the base's is unchanged.
133+
**Customizing an installed package (ADR-0146):** Customize an installed base `hotcrm` in its paired writable package `hotcrm.custom`, which depends on it: a same-named item there with `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` (its `manifest.id`/`manifest.version`) replaces the base's document whole. The read honours it only from the pair (the row's `package_id`), today only for a view, dashboard, report, email_template, translation or flow (the rest of `OVERRIDABLE_METADATA_TYPES` wait for their doors), never judges `basedOn` and ignores any other package's declaration. An email template is one item per (name, locale): an override names a locale the base ships; a locale the base does not ship is added without `overrides`. Then no `?package`, `?package=hotcrm`, the layered read and both lists serve it; the env-wide list holds one item per name one code package ships (a dependent's stays under its own `?package=`); a package-unbound Setup row still wins over the base's until D11. An undeclared pair row or a dependent's row is an orphan in the layered read's `overlay`, `effective` = the base's. An override may narrow a public form's anonymous intake, never re-open one any honoured body withdrew. `GET /packages/hotcrm.custom/export` carries it with `overrides`; the base's is unchanged.
135134

136135
---
137136

@@ -227,7 +226,7 @@ my-app/
227226

228227
## Driver Selection Guide
229228

230-
Drivers are the storage layer. Pick based on your environment:
229+
Drivers are the storage layer:
231230

232231
| Driver | Package | Best For | Notes |
233232
|:-------|:--------|:---------|:------|
@@ -291,8 +290,6 @@ routes added to HttpDispatcher work automatically**.
291290

292291
## Runtime Boot Sequence
293292

294-
Understanding how ObjectStack starts helps debug and customize:
295-
296293
```
297294
objectstack.config.ts
298295
└── defineStack({ manifest, objects, views, ... })

0 commit comments

Comments
 (0)