Skip to content

Commit 0e42aef

Browse files
docs(skills): the platform skill names every type a pair's override is served for, and that removing the pair's item serves the base's again (#30439)
Fixes #30229 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:`skills/objectstack-platform/SKILL.md` 的 ADR-0146 段落。定制包(`hotcrm.custom`)覆盖项的生效类型从原文的 6 种改为平台今天实际服务的 12 种(view、dashboard、report、email_template、translation、flow、page、app、dataset、doc、book、mapping),并补一句:删除定制包里的覆盖项(`DELETE …?package=hotcrm.custom`)即重新服务基础包原件。该技能的 evals 新增两例:dataset/page 覆盖、撤销覆盖。 - **为什么改**:客户的 AI agent 读这份 skill 来写 `.custom` 包。旧文案少列 6 种类型且只字不提撤销,它要么拒绝一个本可做的 page/dataset 定制,要么告诉客户定制无法撤销。 - **风险与回滚**:纯文案与 eval 夹具,不碰任何运行时代码。token 棘轮 6000 → 6009(上限 6011):新增以删掉同段两处重复表述付账,不是改折行。回滚 = revert 本 PR。 - **先看**:`skills/objectstack-platform/SKILL.md` 第 128 行(三处改动都在这一行:类型清单、撤销一句、两处删减);其次 `skills/objectstack-platform/evals/config-plugins-ops.json` 新增 id 13、14。 - **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs`,预期一行 `skills/objectstack-platform/SKILL.md is 6009 tokens (ceiling 6011; headroom 2)` 且 exit 0;`git grep -n "today only for a view" skills/` 预期零命中。 - **需要你做的**:`skills/**` 为 Tier H,需人工批准后合并;审阅通过即合并本 PR。 ## What changes One paragraph of `skills/objectstack-platform/SKILL.md` ("Customizing an installed package (ADR-0146)", line 128), read against `origin/main` `fdd06415`: - **The served-type list.** "today only for a view, dashboard, report, email_template, translation or flow" becomes "for a view, dashboard, report, email_template, translation, flow, page, app, dataset, doc, book or mapping". That is what the reads serve today: the five overlay types by their overlay path; `flow` by `declinesStoredRow`'s first branch (`packages/metadata-protocol/src/protocol-lock-state.ts`, ADR-0146 D9 item 1); `page`, `app`, `dataset`, `doc`, `book` and `mapping` by `SEALED_ROW_SERVED_OVERRIDE_TYPES` (`packages/metadata-protocol/src/protocol-package-bindings.ts`, `mapping` the sixth since PR #30226). - **The revert.** "…the layered read and both lists serve it, and removing the pair's item (`DELETE …?package=hotcrm.custom`) serves the base's again" — `isPairRowRemoval` in `packages/metadata-protocol/src/protocol-packaged-base-refusal.ts` admits a removal naming the pair for every open override type, and the base's document is served once the row is gone (pinned by `protocol.named-base-overlay-write.test.ts`, "once the pair's row is deleted, the read serves the base row again"). - **Paid for inside the same paragraph** (content deleted, not re-wrapped; each rule keeps another home): "and ignores any other package's declaration" (the next paragraph: "Only the pair declares: the save, publish and install doors, `os validate` and `os build` refuse `overrides` from any other package"), and ", never by a base-named or package-less read" (the same sentence already says "served only under its own `?package=`"). - **Evals** (`skills/objectstack-platform/evals/config-plugins-ops.json`): id 13, a dataset-and-page override declared from `hotcrm.custom`; id 14, undoing an override by removing the pair's item. Both cases' `must_contain` tokens are taught by `SKILL.md` (checked by hand: `hotcrm.custom`, `overrides:`, `dataset`, `page`, `DELETE`, `?package=hotcrm.custom`). Case 13's `must_not_contain` is the two `PUT` routes alone (patch round). - Changeset `.changeset/30229-adr0146-override-types.md`: `'@objectstack/skills': patch`, carrying both machine lines. **Why the list and not the rule.** The card prefers a rule that does not age. The code's rule is "every open override type" — `isOpenOverrideType`: disposition `overridable` in `OVERRIDE_TYPE_CLASSIFICATION` AND its ADR-0146 D9 conditions delivered (`CONDITIONS_DELIVERED` = `flow` alone). The spec's exported list `OVERRIDABLE_METADATA_TYPES` (13 names) includes `action`, whose override the write door still refuses as `declared_type_not_yet_available`; naming that symbol would promise one type the platform refuses, and "overridable" has no shorter customer-facing phrasing than the twelve names (the excluded types — permission set, position, capability, object — are already named in the next paragraph). So the paragraph names the current list; when `action`'s door is cut it gains one word. **`skills/**` readings** (the two the lane owes): | reading | before (`fdd06415`) | after (`e3c97f55`) | |:--|:--|:--| | `skills/objectstack-platform/SKILL.md` lines | 426 | 426 | | `skills/objectstack-platform/SKILL.md` tokens (`check-skills-token-ratchet`) | 6000 / ceiling 6011 | 6009 / ceiling 6011 (headroom 2) | | whole package, every `skills/*/SKILL.md` (11 files) lines | 4534 | 4534 | | whole published bundle tokens (ratchet's "bundle total") | 225708 | 226239 (+9 in `SKILL.md`; +522 in the eval fixture, measured but not ratcheted) | ## Tests - `node scripts/check-skills-token-ratchet.mjs` at `e3c97f55`: `skills/objectstack-platform/SKILL.md is 6009 tokens (ceiling 6011; headroom 2)` — exit 0. - `pnpm --filter @objectstack/spec run check:skill-docs` — exit 0; `pnpm --filter @objectstack/spec run check:skill-refs` — exit 0; `pnpm check:doc-authoring` — exit 0; `pnpm check:skill-top-level-keys` — exit 0. - `@objectstack/skills` is the one package this diff ships through (its `build` is `sync-catalog.mjs`, a byte copy of `skills/**`): built under the shared verify lock (`VERDICT command-exit 0`), then `node packages/skills/scripts/sync-catalog.mjs --check` — `97 file(s) under dist/skills are byte-identical to skills/**`, exit 0; `packages/skills/dist/skills/objectstack-platform/SKILL.md` carries the new sentence and no longer carries "today only for a view"; the dist eval file carries id 14. The package declares no `test` or `typecheck` script, so ② is empty for it. - The evals JSON parses (14 cases, ids 1–14). - No ablation: no tested runtime behaviour changed (docs and an eval fixture). ## Acceptance notes - NOT MEASURED locally, left to CI's `Lint & Repo Gates`: `pnpm check:dts-closure`, `pnpm check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure`, `pnpm check:sourcemap-no-sources-content` — each exited 3 (`PREREQUISITE NOT MET`: they read every package's built `dist/`, a full workspace build this doc-only diff cannot move). A declared narrowing, not a skipped family. - `pnpm check:dev-prereqs` (local-only family, owed because the lock run built `packages/spec`): exit 1, "the workspace is not built — 103 of 107 packages declare an entry under dist/ that is not on disk" — the fresh worktree's state, not a finding about this diff; `pnpm check:skill-frame-freshness`: exit 0. - Observation, not filed (zero pull): the `must_contain` token oracle `ui/scripts/check-skill-eval-tokens.mjs` scans `ui/skills` only (`SCAN_ROOTS = ['skills']`, read under `ui/`), so the root `skills/*/evals/*.json` have no mechanical "is this token taught" check; the two new cases were checked by hand against `SKILL.md`. - Observation, not filed (boundary, not a trap): `OVERRIDABLE_METADATA_TYPES` in `@objectstack/spec` names `action` while the write door refuses an `action` override as not yet available (ADR-0146 D9, by design, with a message that says so). The skill text follows the door, not the spec list. ## Verification Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on the actual diff (45 families; `--tier`: the MANDATORY tier, clause ①, `skills/**`, no one-line exit). Head `e3c97f55` (patch round: contract review 6657115315's should-fix — eval 13's `must_not_contain` no longer carries `only for views`, a substring of its own prompt; nothing else changed). The 45-family run below was taken at `75b0062c`; re-run at `e3c97f55` for the changed path: evals JSON parses (14 cases), `node scripts/check-skills-token-ratchet.mjs` exit 0 (SKILL.md 6009 / 6011; eval fixture 3251 tokens), `pnpm --filter @objectstack/spec run check:skill-refs` exit 0, `pnpm check:doc-authoring` exit 0. Every command run in the worktree, exit code captured before any pipe; `--ran` reconciliation: `45 derived famil(ies) accounted for — 41 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)`. <details> <summary>Exit codes, one per derived command (plus check:skill-refs beyond the union)</summary> ```text 0 node scripts/check-adr-0087-registration.mjs --base origin/main 0 node scripts/check-adr-0087-registration.mjs --self-test 0 node scripts/check-changeset-no-major.mjs --base origin/main 0 node scripts/check-closing-keyword-parity.mjs 0 node scripts/check-closing-keyword-parity.mjs --self-test 0 node scripts/check-comment-mask-corpus.mjs 0 node scripts/check-doc-route-spelling.mjs --advisory 0 node scripts/check-doc-route-spelling.mjs --self-test 0 node scripts/check-dts-emitted.mjs --self-test 0 node scripts/check-empty-changeset.mjs --base origin/main 0 node scripts/check-empty-changeset.mjs --self-test 0 node scripts/check-skills-token-ratchet.mjs 0 node scripts/check-skills-token-ratchet.mjs --self-test 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test 0 node scripts/ci/plan-test-core-shards.mjs --self-test 0 node scripts/ci/prune-turbo-cache.mjs --self-test 0 node scripts/pm/release-rehearsal-clone.mjs --self-test 0 node scripts/release-pending-publish.mjs --self-test 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions (first run exit 3 — formula and lint not built; re-run after the targeted build: 0) 0 pnpm --filter @objectstack/spec run check:skill-docs 0 pnpm check:agent-test-spelling 0 pnpm check:changeset-gate-self-tests 0 pnpm check:ci-filter-parity 0 pnpm check:closed-ceremony-text 0 pnpm check:corpus-claim-drift 0 pnpm check:cross-package-test-inputs 0 pnpm check:doc-authoring 0 pnpm check:driver-memory-census 3 pnpm check:dts-closure NOT MEASURED (needs full workspace build) 3 pnpm check:dual-build-cjs-loads NOT MEASURED (needs full workspace build) 0 pnpm check:gitlink-declared 3 pnpm check:lean-entry-closure NOT MEASURED (needs @objectstack/objectql built) 0 pnpm check:merge-driver 0 pnpm check:nul-bytes 0 pnpm check:pm-governed-merges 0 pnpm check:pm-skill-id-lint 0 pnpm check:published-files 0 pnpm check:refd-timer-probe 0 pnpm check:role-word 0 pnpm check:skill-compatibility 0 pnpm check:skill-frame-sync 0 pnpm check:skill-identifier-liveness 0 pnpm check:skill-top-level-keys 3 pnpm check:sourcemap-no-sources-content NOT MEASURED (needs full workspace build) 0 pnpm check:watch-hint-literal 0 pnpm --filter @objectstack/spec run check:skill-refs (beyond the union; PM-named) ``` </details> <details> <summary>Ratchet line and shipped-catalog proof at 75b0062</summary> ```text ✓ check-skills-token-ratchet: skills/objectstack-platform/SKILL.md is 6009 tokens (ceiling 6011; headroom 2). ✓ check-skills-token-ratchet: skills/objectstack-platform/evals/config-plugins-ops.json is 3258 tokens — eval fixture, measured but not ratcheted. ✓ check-skills-token-ratchet: 57 authored bundle file(s) within their ceilings; 10 generator-owned file(s) and 29 eval fixture(s) measured, not ratcheted. os-verify-lock: VERDICT command-exit 0 · held the lock 93s (1m33s) · waited 0s (turbo build: @objectstack/formula, @objectstack/lint, @objectstack/skills + closure; Tasks: 5 successful, 5 total) ✓ @objectstack/skills: 97 file(s) under dist/skills are byte-identical to skills/**. ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_012MmDsR4AZoJZyzKQcBEESh)_ <!-- signed: domain:skills#2 · os-lizheng · session_012MmDsR4AZoJZyzKQcBEESh --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 73b712f commit 0e42aef

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the platform skill names every type a pair's declared override is served for, and that removing the pair's item serves the base's again
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-platform/SKILL.md`, the "Customizing an installed package (ADR-0146)" paragraph: a `.custom` pair's declared override is served for a view, dashboard, report, email_template, translation, flow, page, app, dataset, doc, book or mapping (the paragraph named six), and removing the pair's item (`DELETE …?package=hotcrm.custom`) serves the base's document again (ADR-0146's revert, which the paragraph did not say). The package's evals gain a dataset-and-page override case and a removal case.

‎skills/objectstack-platform/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ to a new protocol major — never to silence a mismatch.
125125

126126
**`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.
127127

128-
**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, 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 pre-D11 unbound Setup row wins over the base's until `os migrate packageless-overlays` moves it, and once movable as a declared override, its package-less save, publish, reset and rollback are refused (D11); an email-template row at a locale the base does not ship moves as the pair's own member and keeps that write. The base's address serves the pair's added locale too (both lists, the layered read with `code` null and `effective` the pair's, the execution list, a send in that locale); a locale a dependent adds under the base's name is served only under its own `?package=`, never by a base-named or package-less read. An undeclared pair row or a dependent's row at a locale the base ships 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`.
128+
**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`), for a view, dashboard, report, email_template, translation, flow, page, app, dataset, doc, book or mapping, and never judges `basedOn`. 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, and removing the pair's item (`DELETE …?package=hotcrm.custom`) serves the base's again; the env-wide list holds one item per name one code package ships (a dependent's stays under its own `?package=`); a pre-D11 unbound Setup row wins over the base's until `os migrate packageless-overlays` moves it, and once movable as a declared override, its package-less save, publish, reset and rollback are refused (D11); an email-template row at a locale the base does not ship moves as the pair's own member and keeps that write. The base's address serves the pair's added locale too (both lists, the layered read with `code` null and `effective` the pair's, the execution list, a send in that locale); a locale a dependent adds under the base's name is served only under its own `?package=`. An undeclared pair row or a dependent's row at a locale the base ships 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`.
129129

130130
The pair comes from `POST /api/v1/packages/hotcrm/ensure-customization`: `namespace` = the base's + `_custom`, `dependencies: { hotcrm: '^<major>' }`; `GET /packages/hotcrm/customizations` lists its overrides, extensions and switched-off items, and uninstalling the base is refused (`409`) while the pair exists. Only the pair declares: the save, publish and install doors, `os validate` and `os build` refuse `overrides` from any other package, and a dependent's item under a base item's name — its one fix is a new name. A permission set, position or capability is never overridden: copy it under a new name ("new similar", landing in the pair). An object is extended (`__c` fields, **objectstack-data**); a flow is overridden or copied (**objectstack-automation**).
131131

‎skills/objectstack-platform/evals/config-plugins-ops.json‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,40 @@
215215
"\"private_key\""
216216
]
217217
}
218+
},
219+
{
220+
"id": 13,
221+
"prompt": "HotCRM (`hotcrm`, 3.2.0) ships the `crm_pipeline` dataset and the `crm_sales_overview` page. Can we replace both with our own the way we replaced its `crm_account_grid` view, or is that only for views?",
222+
"expected_output": "The same declaration, both types. In `hotcrm.custom`, an item of the same name and type with `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` in its body replaces the base's dataset or page whole, and every read of the name serves it: a pair's declared override is served for a view, dashboard, report, email_template, translation, flow, page, app, dataset, doc, book or mapping. Not an edit of HotCRM's read-only item, not a package-unbound Setup row, and not an object extension (`__c` fields add to an object and replace no dataset or page).",
223+
"files": [],
224+
"assertions": {
225+
"must_contain": [
226+
"hotcrm.custom",
227+
"overrides:",
228+
"dataset",
229+
"page"
230+
],
231+
"must_not_contain": [
232+
"PUT /api/v1/meta/dataset/crm_pipeline",
233+
"PUT /api/v1/meta/page/crm_sales_overview"
234+
]
235+
}
236+
},
237+
{
238+
"id": 14,
239+
"prompt": "We overrode HotCRM's `crm_account_grid` view from `hotcrm.custom`; the customer wants HotCRM's original back. How do we undo it?",
240+
"expected_output": "Remove the pair's item: `DELETE /api/v1/meta/view/crm_account_grid?package=hotcrm.custom`, the removal naming the pair. The base's own document is served again by every read of the name (no `?package`, `?package=hotcrm`, the layered read, both lists), and the pair's export no longer carries the item. Not an edit of HotCRM's item, not a rollback of the base's row (its package-less rollback is refused), and not a second override that copies the base's body into `hotcrm.custom`.",
241+
"files": [],
242+
"assertions": {
243+
"must_contain": [
244+
"DELETE",
245+
"?package=hotcrm.custom"
246+
],
247+
"must_not_contain": [
248+
"/rollback",
249+
"PUT /api/v1/meta/view/crm_account_grid"
250+
]
251+
}
218252
}
219253
]
220254
}

0 commit comments

Comments
 (0)