Skip to content

Commit 504ba51

Browse files
docs(skills,docs): the ADR-0126 §1.3 promise pages and the skills teach what ADR-0146 D2 delivers (#28577) (#29264)
domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J <!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67x9nCeopiWqGPokkVD5XHp1n)_ Fixes #28577 Part of #28236 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:文档和技能改成教 ADR-0146 D2 已落地的定制路径。两张承诺页(`capabilities/integrations`、`build-without-code`)不再说视图仪表板「可直接编辑」、不再把读者送去 Setup 的打包自动化页克隆流程,改说:装好的应用在 Studio 里定制,每处改动落在配对包 `<base>.custom`,视图/仪表板/报表/流程是覆盖,对象加 `__c` 字段,流程可本部署关闭或「新建类似」。另外六页(安装模式、对象扩展、元数据生命周期、权限集、CLI、元数据 API)补上 `__c`、`os migrate packageless-overlays`、无包写入已关闭、权限集只能「新建类似」等事实。技能:`objectstack-data` 教 `.custom` 里的对象扩展与 `__c` 保留后缀;`objectstack-automation` 教打包流程的同名声明覆盖(一个开关、挂起运行按旧版完成)与「新建类似」克隆门;`objectstack-platform` 在 #28509 那段之后补 ensure 门、customizations 读、卸载拒绝、「只有配对包能声明、依赖包改名」、三种不可覆盖类型走「新建类似」。三个技能各加一条 eval。 - **为什么改**:卡 #28577(N-30)。七张阻塞卡全部落地后,AI 作者读技能仍会写旧形态:无包覆盖行、不带 `__c` 的字段、双跑的克隆。每句教学都对照 `origin/main` `14208f2c` 上的代码核过,并在下文 What changes 里点名落地 PR。 - **风险与回滚**:纯文本,不改 spec、运行时或 UI。技能受 token 棘轮约束:新增全部由同文件删重复支付(布局树、搜索镜像维护表、公式旁白、日期/金额的 legacy→CEL 行、CEL 提示框、三段 `runAs`/`readonly` 收紧),三个被触文件上限下调到实测值(`objectstack-data/SKILL.md` 保持 6123),无一上调。回滚即 revert。 - **先看**:① `skills/objectstack-platform/SKILL.md` :134 新段;② `skills/objectstack-automation/SKILL.md` :50 新段与被删的两行 legacy→CEL;③ `content/docs/data-modeling/object-extensions.mdx` 新节「Customizing an installed package's object」。 - **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs | grep -E 'objectstack-(data|platform|automation)/SKILL|examples-objects'`,预期四行都是 `(+0)`。 - **需要你做的**:无,席位落地(Tier H:技能面需授权批准)。 ## What changes Every taught sentence cites the PR that landed the behaviour, all on `origin/main` `14208f2c`: - **Promise pages (Wanted 1).** `content/docs/capabilities/integrations.mdx` and `content/docs/build-without-code.mdx` say a customization is an item of the paired `.custom` package made in Studio (#28512 ensure door, #28711 / #28824 Studio customize + Restore package original, #28747 one surface, #28818 Setup metadata pages redirect to Studio), an object gains `__c` fields (#28724, #28860 Studio Add field), a flow is overridden or copied with "new similar" (#28693, #28621, #28668), the switch is on the item (#28420), and "new similar" for permission sets (#28636, #28669). - **Measured pages (Wanted 3).** `deployment/install-modes.mdx` ("customise beside it" → the pair, linked); `data-modeling/object-extensions.mdx` (the tenant row, the `__c` reservation under *Naming the fields you add*, a new section *Customizing an installed package's object* with the `hotcrm.custom` manifest shape — #28724, #28757, #28811, #28512); `concepts/metadata-lifecycle.mdx` (the overlay tier no longer opens a write door, D11 note — #28850, #28689; the two-tier gate's first bullet; the `flow` Regime C row names the declared override, its pinning and "new similar" — #28693, #28500, #28621; the `object` row points at the `__c` extension); `permissions/permission-sets.mdx` (Studio's catalog address after #28818; "new similar" into the pair, never an override, D6 — #28636); `deployment/cli.mdx` (the `os migrate packageless-overlays` row and its occupancy row — #28689); `api/metadata-api.mdx` (the package-less `PUT` onto a packaged item is refused naming the pair; only the pair declares; the ensure and customizations doors — #28850, #28554, #28512, #28595). `automation/flows.mdx` and `plugins/packages.mdx` were read: neither teaches a customization path (their only "overlay" / "clone" hits are a flow-runner overlay and a plugin's export list), so neither moves. - **Skills (Wanted 2).** - `objectstack-data/SKILL.md` § Object Extension Model: an installed package's object is extended from the pair, never replaced and takes no `overrides` (#28554 `declared_object`); every added field ends in `__c`, the suffix is refused outside the pair and an unsuffixed field is refused in it, at save / publish / install, `os validate` and `os build` (#28724, #28757); a base field changes only through `fieldPatches` (#28728, already taught); Studio's Add field writes the same `object_extension` item (#28811, #28860). `references/examples-objects.md`'s lock table reads "Edit", not "Edit (overlay)". Eval 5. - `objectstack-automation/SKILL.md` § Flow Types: a packaged flow is customized by a same-named declared override in the pair, armed in the base's place with one activation switch (`/toggle`) — #28693, #28770; a suspended run finishes on the body it started on — #28500; an undeclared same name is refused at publish — #28554; the clone door is "new similar": switched off, in the pair, never package-less, `409` for a flow holding a credential — #28621. Eval 13. - `objectstack-platform/SKILL.md` § Manifest Reference, one paragraph after #28509's (not rewritten): the ensure door and its two manifest facts (`<ns>_custom`, `^<major>` — #28512), `GET /packages/:id/customizations` (#28595), the uninstall refusal (#28506), the write gate's rule for every other package — rename (#28554), and "new similar" for the three excluded types (#28636). Eval 11. - `objectstack-ui` and `objectstack-studio-admin`: the card lists them as unmeasured. `skills/objectstack-studio-admin` does not exist on `main` (neither under `skills/` nor `.claude/skills/`). `objectstack-ui` names no customization path (its "overlay" hits are the React page overlay and a chart series), so it does not move. - **Token ratchet.** Paid in the same file, by deletion: platform — the project layout tree (its facts stay as one paragraph; the Template section and the Naming table already carry the barrels and the file pattern); data — the search-mirror maintenance table and the `formula`-mirror aside, both restating the sentence above them; automation — the dates and money legacy → CEL bullets (`objectstack-formula`'s table), the CEL callout's restated pointer, the blueprint's restated layout, and three `runAs` / `readonly` notes tightened with no rule removed. Ceilings lowered to the measurement: `objectstack-platform/SKILL.md` 6012 → 6011, `objectstack-automation/SKILL.md` 6450 → 6449, `examples-objects.md` 1769 → 1756; `objectstack-data/SKILL.md` stays 6123 at 6123. No ceiling rises. - **Changeset.** `.changeset/28577-adr-0146-skills-docs-teaching.md`, `@objectstack/skills` patch, both declaration lines. **Skills bundle readings** (ceil(bytes / 4), release-stamp line excluded; lines before → after): | File | tokens before → after | lines before → after | |:--|:--|:--| | `skills/objectstack-data/SKILL.md` | 6114 → 6123 | 468 → 466 | | `skills/objectstack-data/references/examples-objects.md` | 1769 → 1756 | 197 → 197 | | `skills/objectstack-automation/SKILL.md` | 6450 → 6449 | 450 → 452 | | `skills/objectstack-platform/SKILL.md` | 6011 → 6011 | 456 → 435 | | whole bundle, authored (57 files) | 181927 → 181922 | — | | all `SKILL.md` lines | 4408 → 4387 | | ## Why ADR-0126 §8 item 5 asks the §1.3 promise pages to say what the customization model delivers once it has landed, "not before"; ADR-0146's rollout row N-30 is that correction, and every `Blocked-by:` card of #28577 closed by 2026-11-02 (the last, #28576, through #28850). Until this PR the skills still taught the paths D2, D4, D10 and D11 superseded. ## Verification - `pnpm docs:build` — exit 0 under the verify lock (`VERDICT command-exit 0`, held 191 s); both new anchors (`#customizing-an-installed-packages-object`, `#customizing-an-installed-package-the-paired-custom-override-adr-0146`) resolve in the built pages. - `pnpm check:docs-spec-enumerations`, `pnpm check:docs-audit-scope`, `pnpm check:skill-identifier-liveness`, `pnpm check:doc-authoring` — exit 0 each. - `node scripts/docs-audit/affected-docs.mjs origin/main` — "0 docs name something this change touched" (no `packages/**` change, as expected for a docs-and-skills PR); the accuracy pass was done by hand: each taught sentence was read against the door or test that pins it (`protocol-packaged-base-refusal.ts#judgeOverrideWrite`, `packagelessOverlayWriteRefusal`, `protocol-extension-field-suffix.ts`, `protocol-object-extension-item.ts`, `protocol-customization-package.ts`, `commands/migrate/packageless-overlays.ts`, the studio i18n strings). - `node scripts/check-skills-token-ratchet.mjs` and `--self-test` — exit 0 (57 authored files within ceilings; self-test 97 cases). - `pnpm --filter @objectstack/spec run gen:skill-docs` / `gen:skill-refs` — regenerated, no diff. - Derived gate family (`node scripts/pm/dispatch-gates.mjs --commands`, 92 commands) run on head `b91f8e6e8`: 89 of 92 exited 0 on the first pass; `check:skill-examples` refused (exit 3) for want of built declarations and exits 0 once `@objectstack/spec`, `@objectstack/client` and `@objectstack/client-react` are built with declarations (281 marked blocks, including the new `defineObjectExtension` block in `object-extensions.mdx`); `check:dts-closure` and `check:dual-build-cjs-loads` are not measured in this tree (no full `pnpm build`; the diff touches no `packages/**`), so CI's run of those two is the reading. `node scripts/pm/dispatch-gates.mjs --ran` on the recorded exit codes: 92 derived, 91 run, 1 NOT-MEASURED, 0 UNRUN. <details><summary>Gate readings</summary> | # | command | exit | |:--|:--|:--| | 1 | `node scripts/check-adr-0087-registration.mjs --base origin/main` | 0 | | 2 | `node scripts/check-adr-0087-registration.mjs --self-test` | 0 | | 3 | `node scripts/check-adr-links.mjs` | 0 | | 4 | `node scripts/check-adr-links.mjs --self-test` | 0 | | 5 | `node scripts/check-changeset-no-major.mjs --base origin/main` | 0 | | 6 | `node scripts/check-closing-keyword-parity.mjs` | 0 | | 7 | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | 8 | `node scripts/check-comment-mask-corpus.mjs` | 0 | | 9 | `node scripts/check-declaration-mirrors.mjs` | 0 | | 10 | `node scripts/check-declaration-mirrors.mjs --self-test` | 0 | | 11 | `node scripts/check-doc-route-spelling.mjs --advisory` | 0 | | 12 | `node scripts/check-doc-route-spelling.mjs --self-test` | 0 | | 13 | `node scripts/check-docs-section-name.mjs` | 0 | | 14 | `node scripts/check-docs-section-name.mjs --self-test` | 0 | | 15 | `node scripts/check-dts-emitted.mjs --self-test` | 0 | | 16 | `node scripts/check-empty-changeset.mjs --base origin/main` | 0 | | 17 | `node scripts/check-empty-changeset.mjs --self-test` | 0 | | 18 | `node scripts/check-section-landing-index.mjs` | 0 | | 19 | `node scripts/check-section-landing-index.mjs --self-test` | 0 | | 20 | `node scripts/check-self-test-wired.mjs` | 0 | | 21 | `node scripts/check-self-test-wired.mjs --self-test` | 0 | | 22 | `node scripts/check-self-test-workflow-commands.mjs` | 0 | | 23 | `node scripts/check-self-test-workflow-commands.mjs --self-test` | 0 | | 24 | `node scripts/check-skills-token-ratchet.mjs` | 0 | | 25 | `node scripts/check-skills-token-ratchet.mjs --self-test` | 0 | | 26 | `node scripts/check-whole-set-label-write.mjs` | 0 | | 27 | `node scripts/check-whole-set-label-write.mjs --self-test` | 0 | | 28 | `node scripts/ci/assert-turbo-remote-cache.mjs --self-test` | 0 | | 29 | `node scripts/ci/plan-test-core-shards.mjs --self-test` | 0 | | 30 | `node scripts/ci/prune-turbo-cache.mjs --self-test` | 0 | | 31 | `node scripts/pm/bare-root-worklist.mjs --self-test` | 0 | | 32 | `node scripts/pm/release-rehearsal-clone.mjs --self-test` | 0 | | 33 | `node scripts/release-pending-publish.mjs --self-test` | 0 | | 34 | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 | | 35 | `pnpm --filter @objectstack/lint run check:doc-security-posture` | 0 | | 36 | `pnpm --filter @objectstack/spec run check:docs` | 0 | | 37 | `pnpm --filter @objectstack/spec run check:empty-state` | 0 | | 38 | `pnpm --filter @objectstack/spec run check:liveness` | 0 | | 39 | `pnpm --filter @objectstack/spec run check:skill-docs` | 0 | | 40 | `pnpm --filter @objectstack/spec run check:skill-examples` | 3 → 0 (refused: `packages/spec/dist` unbuilt; after `pnpm --filter @objectstack/spec build` and `pnpm --filter "@objectstack/client-react..." build`, re-run on the same head: `✅ 281 prose examples type-check across 3 surface(s)`, exit 0) | | 41 | `pnpm --filter @objectstack/spec run check:skill-refs` | 0 | | 42 | `pnpm --filter @objectstack/spec run check:strictness-ledger` | 0 | | 43 | `pnpm --filter @objectstack/spec run check:variant-docs` | 0 | | 44 | `pnpm --filter @objectstack/spec run check:yaml-examples` | 0 | | 45 | `pnpm check:agent-test-spelling` | 0 | | 46 | `pnpm check:bash32-floor` | 0 | | 47 | `pnpm check:changeset-gate-self-tests` | 0 | | 48 | `pnpm check:ci-filter-parity` | 0 | | 49 | `pnpm check:cli-command-ids` | 0 | | 50 | `pnpm check:cli-examples-parity` | 0 | | 51 | `pnpm check:closed-ceremony-text` | 0 | | 52 | `pnpm check:corpus-claim-drift` | 0 | | 53 | `pnpm check:cross-package-test-inputs` | 0 | | 54 | `pnpm check:doc-anchors` | 0 | | 55 | `pnpm check:doc-authoring` | 0 | | 56 | `pnpm check:doc-frontmatter` | 0 | | 57 | `pnpm check:docs-audit-scope` | 0 | | 58 | `pnpm check:docs-redirects` | 0 | | 59 | `pnpm check:docs-single-h1` | 0 | | 60 | `pnpm check:docs-spec-enumerations` | 0 | | 61 | `pnpm check:docs-transcript-drift` | 0 | | 62 | `pnpm check:driver-memory-census` | 0 | | 63 | `pnpm check:dts-closure` | 1 (not measured here: 14 packages have no built `dist/` in this tree; the diff touches no `packages/**`, so CI's run is the reading) | | 64 | `pnpm check:dual-build-cjs-loads` | 3 (prerequisite not met: 72 packages have no `dist/`; same note) | | 65 | `pnpm check:entry-guard` | 0 | | 66 | `pnpm check:gitlink-declared` | 0 | | 67 | `pnpm check:lean-entry-closure` | 0 | | 68 | `pnpm check:merge-driver` | 0 | | 69 | `pnpm check:nul-bytes` | 0 | | 70 | `pnpm check:overlay-whitelist-table` | 0 | | 71 | `pnpm check:parse-guard` | 0 | | 72 | `pnpm check:pm-changeset-deadline-census` | 0 | | 73 | `pnpm check:pm-dispatch-gates` | 0 | | 74 | `pnpm check:pm-governed-merges` | 0 | | 75 | `pnpm check:pm-skill-id-lint` | 0 | | 76 | `pnpm check:pnpm-filter-targets` | 0 | | 77 | `pnpm check:pnpm-flag-spellings` | 0 | | 78 | `pnpm check:published-files` | 0 | | 79 | `pnpm check:published-readme-links` | 0 | | 80 | `pnpm check:ratchet-remedy-authority` | 0 | | 81 | `pnpm check:react-page-adapter-contract` | 0 | | 82 | `pnpm check:refd-timer-probe` | 0 | | 83 | `pnpm check:role-word` | 0 | | 84 | `pnpm check:scripts-symbol-anchors` | 0 | | 85 | `pnpm check:skill-compatibility` | 0 | | 86 | `pnpm check:skill-frame-sync` | 0 | | 87 | `pnpm check:skill-identifier-liveness` | 0 | | 88 | `pnpm check:skill-top-level-keys` | 0 | | 89 | `pnpm check:sourcemap-no-sources-content` | 0 | | 90 | `pnpm check:system-context-census` | 0 | | 91 | `pnpm check:vendor-version-stamps` | 0 | | 92 | `pnpm check:watch-hint-literal` | 0 | </details> ## Human Minimum Each item is one rule from [CONTRIBUTING.md › Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum), which links the AGENTS.md text that states it. - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (no schema touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (repo-wide lint is CI's run) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8VVAjSK8WEK7Uja5NRpTo --- _Generated by [Claude Code](https://claude.ai/code/session_01J8VVAjSK8WEK7Uja5NRpTo)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9189956 commit 504ba51

17 files changed

Lines changed: 485 additions & 157 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the ADR-0126 §1.3 promise pages and the skills teach what ADR-0146 D2 delivers — customizations in `.custom`, `__c` fields, the flow override and "new similar", no package-less overlay
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The skills taught the pre-epic paths: `objectstack-data` named no home for a customer's extension of a shipped object and no `__c` rule, and its lock table still called an edit of a shipped item an "overlay"; `objectstack-automation` said nothing about a flow an installed package ships, so an agent cloned it (two copies running) or edited it in place (refused); `objectstack-platform` taught the declared override's read but not who may declare it, how the pair is created, or what the three non-overridable types do. The docs promise pages (`capabilities/integrations`, `build-without-code`) still said views and dashboards are "yours to edit directly" and sent readers to a Setup packaged-automation page to switch off and clone a flow.
11+
- **Now.** `objectstack-data` says an object an installed package ships is extended from the base's paired `.custom` package, every added field ends in `__c`, the suffix is refused anywhere else and an unsuffixed field is refused in the pair, a base field changes only through `fieldPatches`, and Studio's Add field writes the same extension; its lock table reads "Edit", not "Edit (overlay)". `objectstack-automation` says a packaged flow is customized by a same-named declared override in the pair (armed in the base's place, one activation switch, a suspended run finishes on the body it started on), that an undeclared same name is refused at publish, and that the clone door is "new similar": a copy under a new name, switched off, in the pair, never package-less. `objectstack-platform` adds the ensure door and its two manifest facts, the customizations read, the uninstall refusal, the write gate's one rule for dependents (rename), and "new similar" for permission sets, positions and capabilities. One eval per skill asserts the taught shape and refuses the old one.
12+
- Every file stays under its token ceiling: the new text is paid for by deleting restatement in the same file (a layout tree the Template section and the Naming table already state; a search-mirror maintenance table and a formula aside restating the sentence above them; the dates and money legacy → CEL rows `objectstack-formula`'s table holds; the CEL callout restating that skill's pointer; two `runAs` notes and a `readonly` note tightened, no rule removed). The touched ceilings lower to their new measurements; no ceiling rises, no frontmatter, schema or behaviour changes. The docs site's promise pages, install modes, object extensions, metadata lifecycle, permission sets, CLI and metadata API pages gain the same facts.

‎content/docs/api/metadata-api.mdx‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ unaffected. See [unrecognised type spelling](/docs/api/error-catalog#invalid_req
9696
in the error catalog for the envelope — the same code and status cover both this
9797
refusal and the misspelling one above; the message text tells them apart.
9898

99+
**An item an installed package ships is customized in that package's pair**
100+
([ADR-0146](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)
101+
D2, D11). A `PUT` naming no package onto an item one installed read-only package
102+
`hotcrm` ships is refused `403 NOT_OVERRIDABLE`; for a type an override applies to,
103+
the sentence names the route: save it with `?package=hotcrm.custom`, the body
104+
declaring `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` (`basedOn` is the
105+
`manifest.version` of `hotcrm` it was taken from, recorded and never judged). Only the pair declares an override: an item of any other
106+
package that depends on `hotcrm` and bears a `hotcrm` item's name is refused at save
107+
and publish, and its fix is a new name. A permission set, position or capability is
108+
never overridden; it is copied under a new name. What the reads then serve is in
109+
[Metadata lifecycle](/docs/concepts/metadata-lifecycle#customizing-an-installed-package-the-paired-custom-override-adr-0146).
110+
`POST /packages/hotcrm/ensure-customization` creates the pair when absent (Studio calls
111+
it on the first change), and `GET /packages/hotcrm/customizations` lists its overrides,
112+
extensions and switched-off items in one read.
113+
99114
**Body**: `{ type: "object", name: "account", item: { ... } }`
100115
**Response**: `{ success: true }`
101116

‎content/docs/build-without-code.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Two properties make Studio safe to hand to non-developers:
3535

3636
## Setup — where the platform is administered
3737

38-
The **System Settings** app covers platform administration: user management and invitations, permission assignment, datasources (including federated external databases), diagnostics and audit logs, and the **Marketplace** — template apps such as HotCRM install with one click and appear alongside your own apps. What "customize" means depends on the piece: views and dashboards are yours to edit directly; a packaged object grows through your own fields via an extension package, never by reshaping what shipped; a packaged flow stays locked, but a dedicated **packaged automation** page here lets you switch it off and clone your own — the clone is an ordinary flow, yours to edit in Studio.
38+
The **System Settings** app covers platform administration: user management and invitations, permission assignment, datasources (including federated external databases), diagnostics and audit logs, and the **Marketplace** — template apps such as HotCRM install with one click and appear alongside your own apps. Setup holds no metadata editor: its metadata pages open Studio, and an installed app is customized there. Open the app in Studio and every change lands in its paired customization package (`hotcrm.custom` for HotCRM), created on your first change and exported and deployed like any package, while what shipped stays the vendor's. **Customize** on a view, dashboard, report or flow saves your version as an override of the shipped one, marked with the app it overrides, and **Restore package original** removes it. **Add field** on a shipped object adds your own field, named with a `__c` suffix, and a shipped field's label, description, help text, placeholder and option labels can be changed, nothing else. A shipped flow's switch turns it off for this deployment, and **New similar** copies a flow or a permission set under a new name; a flow's copy starts switched off.
3939

4040
## When to reach for code instead
4141

‎content/docs/capabilities/integrations.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ A business system earns its keep by fitting into the systems — and habits —
1515
- **Triggers in** — external systems start platform [flows](/docs/capabilities/automation).
1616
- **Connectors** — ready-made Slack and generic REST connectors drop into flows; an HTTP API, OpenAPI document, or MCP server can be [declared as metadata](/docs/automation/connectors) and goes live at boot, no plugin code.
1717
- **Federated external databases** — point at an existing database and query it in place: no migration, visible in views, usable in flows.
18-
- **Marketplace templates** — install complete apps (HotCRM among them) with one click: objects, views, flows, dashboards, and seed data included. Views and dashboards are yours to edit directly; a packaged flow stays locked, but [Setup](/docs/build-without-code) lets you switch it off and clone your own to edit in Studio; a packaged object grows through your own fields via an extension package, not by editing what shipped.
18+
- **Marketplace templates** — install complete apps (HotCRM among them) with one click: objects, views, flows, dashboards, and seed data included. What shipped stays the vendor's: you customize an installed app in [Studio](/docs/build-without-code), and every change lands in the app's own customization package (`hotcrm.custom` for HotCRM), which exports and deploys to another environment like any package. A view, dashboard, report or flow is customized by overriding it there; a shipped object gains your own fields, named with a `__c` suffix; a shipped flow can be switched off for your deployment, or copied under a new name with **New similar**.
1919
- **Deploy anywhere** — cloud or fully self-hosted; your data and your app definition stay yours.
2020

2121
## Everyday collaboration

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,15 @@ document **whole** for every read that serves the base's name
166166

167167
> **ADR-0131 D6 — the per-organization overlay axis is retired.** The `/meta` doors (REST and the runtime dispatcher) carry no organization into a metadata write or read: a Studio or `PUT /api/v1/meta/...` write of the five ✅ types below lands **environment-wide** (since ADR-0131 D7 the `sys_metadata` family declares no organization column) and is served to every organization, and an organization admin holding only the retired `manage_org_presentation` capability is refused (`403`). The protocol itself refuses every organization-scoped write, and every read — item, list, layers, cached, history, diff, audit, drafts and the commit timeline — is environment → code, whatever organization a caller names. An overlay row an earlier release stored under an organization — under the `single` posture, every Studio save of these types, filed under the Default Organization — is served by no read, and on a SQL database (SQLite, PostgreSQL, MySQL) still holding one a v18 `os serve` refuses to start until the v18 migration ceremony (ADR-0131 C7: `os migrate organization-ownership --out PLAN`, then `--apply --plan-file PLAN`) has carried such a row to the environment layer and dropped the column; nothing deletes or rewrites it before then. The anonymous form doors read the environment alone too: the ceremony carries every organization-layer public-form withdrawal onto the environment, fail-closed. The table still records which types take an overlay of a shipped item.
168168

169+
> **ADR-0146 D11 — the tier no longer opens a write door onto a packaged item.** A write naming no package — a save, a publish, a reset (`DELETE`) or a rollback — onto a ✅-type item that one installed read-only package ships is refused `403 NOT_OVERRIDABLE`, and the sentence names the base's pair: save it into `?package=<base>.custom`, declaring `overrides` ([above](#customizing-an-installed-package-the-paired-custom-override-adr-0146)). A package-less row stored before the close is still served until `os migrate packageless-overlays` moves it into the pair as a declared override; the ✅ row is now the set that ceremony moves and the read path honours for an unmoved row. A package-less write of a name no package ships is environment metadata, and stays open.
170+
169171
| Type | `allowEnvironmentOverlay` | Rationale |
170172
| :--- | :---: | :--- |
171173
| `view`, `dashboard`, `report`, `email_template`, `translation` | ✅ | Pure rendering / render-time content. Customizing it in an environment is safe. |
172174
| `flow` | ❌ | Flows carry execution side-effects (events, jobs, audit), and the `flow` entry declares `supportsOverlay: false` — the loader cannot merge a per-org flow overlay, so the write permission granted a write nothing could read back: an org-scoped flow overlay wrote successfully and **lost its binding on the next cold start**. Rolled back (#6283) so the silent phantom is a loud `403 not_overridable` at the moment of the write. **`allowRuntimeCreate` stays `true`** — a tenant may still author a *brand-new* flow through the runtime API (ADR-0070 package-first authoring); what is closed is overlaying a *packaged* flow. |
173175
| `agent` | ❌ | Agents are platform-owned and closed to third parties (ADR-0063 §2) — no environment fork of a packaged agent. |
174176
| `permission`, `position` | ❌ | Authorization correctness — an overlay of a packaged permission set *is* silent privilege drift (ADR-0005's security row). Rolled back from an unratified `true` by the 2026-08-08 maintainer ruling (#6483); `position` takes the amendment's `false` default for new types. **`allowRuntimeCreate` stays `true`**: runtime-created sets (including package-bound rows materialized through the metadata door) keep working, while an admin-door edit of a *code-declared* set refuses with `403 not_overridable`, naming the sanctioned path: clone the set under a new name and edit the clone ([ADR-0126](#packaged-flows-actions-and-permission-sets-adr-0126)). The package's own author still changes the declaration in the package and re-publishes (ADR-0086 two-doors). |
175-
| `object` | ❌ | Defines the table schema. Overriding a packaged object would break existing data — but `allowRuntimeCreate: true`, so tenants *can* author brand-new objects, **and this is also how a new field is added** (write the object with the field in `fields`). |
177+
| `object` | ❌ | Defines the table schema. Overriding a packaged object would break existing data — but `allowRuntimeCreate: true`, so tenants *can* author brand-new objects, **and this is also how a new field is added** (write the object with the field in `fields`). On an object an installed package ships, a field is added as an object extension in the base's paired `.custom` package, named `<name>__c` ([Object extensions](/docs/data-modeling/object-extensions#customizing-an-installed-packages-object)). |
176178
| `field` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#7893). A field is not a standalone artifact — fields are authored inside their object (`ObjectSchema.fields`), so a `field` write minted a *separate* `sys_metadata` row keyed `('field','<object>.<name>')` that nothing ever composed into the parent: `PUT /meta/field/showcase_task.zz_probe` answered 200 `state=active`, the row read back `_diagnostics.valid: true`, and `GET /meta/object/showcase_task` never listed the field. The door is closed rather than bridged: add a field by writing its **object** (`PUT /meta/object/:name`, or `**/*.object.ts` and redeploy), which both persists and composes. Existing rows are untouched — they were inert before the change too — and remain deletable. |
177179
| `datasource` | ❌ | Connection strings; multi-tenant isolation is enforced at a higher layer. (`allowRuntimeCreate: true` — the datasource wizard persists `origin: 'runtime'` rows.) |
178180
| `job` | ❌ | **Also `allowRuntimeCreate: false` since protocol 17** (#4509). `JobSchema.handler` names a function in the compiled bundle's function table, which a runtime writer has no way to reach — so a job created in Studio or through `PUT /meta` parsed, saved, reported success and was never scheduled. The door is closed rather than bridged: `job` stays first-class through `*.job.ts` / `defineStack({ jobs, functions })`, where every schedule shape, `retryPolicy` and `timeout` does reach the scheduler. Existing rows are untouched — they were never scheduled — and `migrateStoredMetadata` reports them `skipped`. |
@@ -184,7 +186,7 @@ There is no `workflow` metadata type (per [ADR-0020](https://github.com/objectst
184186

185187
The gate is **two-tier** — `allowEnvironmentOverlay: false` is not the same as "no runtime writes":
186188

187-
- Overwriting an item that ships from a code package requires `allowEnvironmentOverlay`. Denied → `403 not_overridable`.
189+
- Overwriting an item that ships from a code package is refused → `403 not_overridable`. The change is a declared override saved into the base's paired `.custom` package ([above](#customizing-an-installed-package-the-paired-custom-override-adr-0146)); for a ✅ type, a package-less row stored before ADR-0146 D11 is still served until it is moved.
188190
- Creating a brand-new item that has no artifact backing requires `allowRuntimeCreate` (or `allowEnvironmentOverlay`). Denied → `403 not_creatable`.
189191

190192
See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md) for the full design and amendments.
@@ -195,7 +197,7 @@ See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/
195197

196198
| Type | Sanctioned path the refusal names |
197199
| :--- | :--- |
198-
| `flow` | Clone it under a new name (`POST /api/v1/automation/:name/clone`, body `{name, label}`), or switch it off (`POST /api/v1/automation/:name/toggle`, body `{enabled: false}`). A flow that holds a credential (an inbound hook's `secret`, an `http` node's `signingSecret`) is not cloned in one step: the clone door refuses it with `409`, because a copy would share the secret, and the copy is authored as a new flow with its own. The copy is written switched off, into the source package's paired customization package `<base>.custom` (created on first use), or into the writable package the body's optional `package` names; a flow no read-only package ships has no pair, so its clone names `package`. The answer's `packageId` says where the copy landed ([ADR-0146](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md) D3, D4). |
200+
| `flow` | Declare an override from the base's paired `<base>.custom`, or rename it (a write from any other package is told to rename: only the pair declares an override), or switch it off (`POST /api/v1/automation/:name/toggle`, body `{enabled: false}`). The declared override is armed in the base flow's place, keeps its one activation switch, and a run suspended on the base finishes on the version it started on (ADR-0146 D9). A copy under a new name is "New similar", the clone door `POST /api/v1/automation/:name/clone` (body `{name, label}`), which the removal refusal still names. A flow that holds a credential (an inbound hook's `secret`, an `http` node's `signingSecret`) is not cloned in one step: the clone door refuses it with `409`, because a copy would share the secret, and the copy is authored as a new flow with its own. The copy is written switched off, into the source package's paired customization package `<base>.custom` (created on first use), or into the writable package the body's optional `package` names; a flow no read-only package ships has no pair, so its clone names `package`. The answer's `packageId` says where the copy landed ([ADR-0146](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md) D3, D4). |
199201
| `action` | Switch it off (`POST /api/v1/actions/_activation/:object/:action`, body `{enabled: false}`; `:object` is `global` for an object-less action). No clone is offered. |
200202
| `permission` | Clone it under a new name: the "Clone" action on the permission set, or the clone door `POST /api/v1/security/permission-sets/:name/clone` (body `{name, label, package?}`) with a new name. The clone door copies the whole definition from the security catalog; a copy of a packaged set lands in the package's paired `<X>.custom` (ADR-0146 D3/D6). |
201203

0 commit comments

Comments
 (0)