From 7390bef4d67963533b28c1979cf4a92bafdcac8b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 21:02:49 +0000 Subject: [PATCH 1/6] docs(skills,docs): an email-template override names a locale its base ships; a new locale carries no overrides Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- .changeset/28778-email-template-override-locale.md | 11 +++++++++++ content/docs/concepts/metadata-lifecycle.mdx | 8 +++++++- skills/objectstack-platform/SKILL.md | 11 ++++------- 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 .changeset/28778-email-template-override-locale.md diff --git a/.changeset/28778-email-template-override-locale.md b/.changeset/28778-email-template-override-locale.md new file mode 100644 index 00000000000..992f1ac0b6c --- /dev/null +++ b/.changeset/28778-email-template-override-locale.md @@ -0,0 +1,11 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): objectstack-platform says an email-template override names a locale its base ships; a new locale carries no `overrides` + +Clause-②: no +Metadata-protocol: none + +- **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. +- **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. diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index cc99db9e390..ab0f3949039 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -115,7 +115,8 @@ later ships. See [ADR-0070](https://github.com/objectstack-ai/objectstack/blob/m A customer's change to an installed base package — HotCRM, id `hotcrm` — is an item of the base's paired writable package **`hotcrm.custom`**, which depends on the base; the -base's own items stay read-only. An item of the pair that bears a base item's name and +base's own items stay read-only. An item of the pair that bears a base item's name — for an +`email_template`, one item per `(name, locale)`, the name **and** a locale the base ships — and declares in its body `overrides: { package: 'hotcrm', basedOn: '3.2.0' }` (the base's `manifest.id` and `manifest.version`; `basedOn` is recorded and never judged; schema: [Override Declaration](/docs/references/kernel/override-declaration)) replaces the base's @@ -131,6 +132,11 @@ document **whole** for every read that serves the base's name or `translation`: the rest of `OVERRIDABLE_METADATA_TYPES` (`@objectstack/spec/kernel`) — `page`, `app`, `dataset`, `doc`, `book`, `mapping`, `flow` and `action` — waits for its doors and is not served yet. A declaration from any other package is ignored. +- **A new locale is not an override.** An `email_template` the pair saves at a locale the + base does not ship is the pair's own member, served under `hotcrm.custom`, and carries no + `overrides`; declaring one there is refused (`403 NOT_OVERRIDABLE` at the save, publish + and install doors, a row from `os validate`, the sentence naming the locale), because the + base ships no such member to replace. - **The environment-wide list** serves one item per name that exactly one code package ships. A dependent package's same-named item is not listed beside it and stays under its own `?package=` address. A package-unbound row — the overlay row of the table diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index bc3d05b51b6..bc1ed3481de 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -55,8 +55,7 @@ ownerless row into a queue — parked, `round_robin` or `least_loaded` — or to user. Both need the ownership-queue service `@objectstack/plugin-sharing` registers (`sharing` in `requires`): a stack declaring either without it is refused at boot. -Undeclared keys are handled per surface, and the two postures are worth -keeping straight: +Undeclared keys are handled per surface, in two postures: - **Refused** — `defineStack()`'s top level, each `objects[]` entry (`ObjectSchema`), and each field (`FieldSchema`). The parse throws, naming @@ -108,7 +107,7 @@ OS_ARTIFACT_PATH=./dist/objectstack.json objectstack dev ## Manifest Reference -Every stack needs a `manifest` to identify itself in the ecosystem: +Every stack needs a `manifest`: ```typescript manifest: { @@ -131,7 +130,7 @@ to a new protocol major — never to silence a mismatch. **`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 `__` 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. -**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. +**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 (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. --- @@ -227,7 +226,7 @@ my-app/ ## Driver Selection Guide -Drivers are the storage layer. Pick based on your environment: +Drivers are the storage layer: | Driver | Package | Best For | Notes | |:-------|:--------|:---------|:------| @@ -291,8 +290,6 @@ routes added to HttpDispatcher work automatically**. ## Runtime Boot Sequence -Understanding how ObjectStack starts helps debug and customize: - ``` objectstack.config.ts └── defineStack({ manifest, objects, views, ... }) From dd9e43d1e5650d6c761501d7ea4646bd5cd21ed7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 21:42:43 +0000 Subject: [PATCH 2/6] docs(skills,docs): flow is an honoured override today; name it beside the other five Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- content/docs/concepts/metadata-lifecycle.mdx | 6 +++--- skills/objectstack-platform/SKILL.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index ab0f3949039..3bc2be9ada2 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -128,9 +128,9 @@ document **whole** for every read that serves the base's name override, `effective` = the override), the list scoped to the base and the environment-wide list all serve the override, stamped `_packageId: 'hotcrm'`. The declaration counts only from the pair — the stored row's own `package_id` must be - `hotcrm.custom` — and today only for a `view`, `dashboard`, `report`, `email_template` - or `translation`: the rest of `OVERRIDABLE_METADATA_TYPES` (`@objectstack/spec/kernel`) - — `page`, `app`, `dataset`, `doc`, `book`, `mapping`, `flow` and `action` — waits for + `hotcrm.custom` — and today only for a `view`, `dashboard`, `report`, `email_template`, + `translation` or `flow`: the rest of `OVERRIDABLE_METADATA_TYPES` (`@objectstack/spec/kernel`) + — `page`, `app`, `dataset`, `doc`, `book`, `mapping` and `action` — waits for its doors and is not served yet. A declaration from any other package is ignored. - **A new locale is not an override.** An `email_template` the pair saves at a locale the base does not ship is the pair's own member, served under `hotcrm.custom`, and carries no diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index bc1ed3481de..b7081c4c39f 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -130,7 +130,7 @@ to a new protocol major — never to silence a mismatch. **`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 `__` 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. -**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 (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. +**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. --- From c0a65fc62f2da157cb68390307cd7a1c8bc72bf7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 23:42:27 +0000 Subject: [PATCH 3/6] docs(skills,docs): a locale added in the `.custom` pair is served on the base's address; a dependent's added locale stays under its own package The ADR-0146 customization paragraph of skills/objectstack-platform/SKILL.md and the ADR-0146 section of content/docs/concepts/metadata-lifecycle.mdx state, beside the override-locale rule, where an added email template locale is served: the pair's on the base's address (both lists, the execution list, a send in that locale), a dependent's only under its own package. The skill's new sentence is paid for by tightening four restating clauses elsewhere in the file; no rule is dropped. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- .changeset/28829-pair-added-locale-served.md | 11 +++++++++++ content/docs/concepts/metadata-lifecycle.mdx | 8 ++++++++ skills/objectstack-platform/SKILL.md | 14 ++++++-------- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 .changeset/28829-pair-added-locale-served.md diff --git a/.changeset/28829-pair-added-locale-served.md b/.changeset/28829-pair-added-locale-served.md new file mode 100644 index 00000000000..e11d087f815 --- /dev/null +++ b/.changeset/28829-pair-added-locale-served.md @@ -0,0 +1,11 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): objectstack-platform says a locale added in the `.custom` pair is served on the base's address, and a locale a dependent adds under the base's name stays under its own package + +Clause-②: no +Metadata-protocol: none + +- **Before.** The ADR-0146 customization paragraph said a locale the base does not ship is added in the pair without `overrides`, and stopped there: nothing said where that added locale is served, and nothing said what a dependent package's added locale under the base's name reaches. The reads of a base's address now decide an email template member by member, so an author could expect the pair's added locale to stay under `hotcrm.custom` only, or a dependent's to answer on the base's address. +- **Now.** One sentence beside that rule: the base's address serves the pair's added locale too (both lists, the execution list, a send in that locale), and 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. The file stays under its token ceiling: the sentence is paid for by tightening four restating clauses elsewhere in the file. No rule is removed, no frontmatter, schema or behaviour changes. diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index 3bc2be9ada2..f6c36040ade 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -137,6 +137,14 @@ document **whole** for every read that serves the base's name `overrides`; declaring one there is refused (`403 NOT_OVERRIDABLE` at the save, publish and install doors, a row from `os validate`, the sentence naming the locale), because the base ships no such member to replace. +- **The pair's added locale is served on the base's address; a dependent's is not.** The + member `hotcrm.custom` adds is listed by `GET /meta/email_template` with no `?package` + and with `?package=hotcrm`, is in the execution list the email service reads, and so is + what a send in that locale renders — stamped `_packageId: 'hotcrm.custom'`, as the + by-name read already served it. A locale another package that depends on `hotcrm` + adds under the base template's name is that package's own item, served only under + `?package=` naming it and in the list scoped to it: the read naming `hotcrm` and the read + naming no package answer `404`, and the base's lists leave it out. - **The environment-wide list** serves one item per name that exactly one code package ships. A dependent package's same-named item is not listed beside it and stays under its own `?package=` address. A package-unbound row — the overlay row of the table diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index b7081c4c39f..36daef8539b 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -44,8 +44,7 @@ are `manifest`, `packages`, `objects`, `objectExtensions`, `picklists`, There is deliberately **no** top-level `workflows` or `approvals` collection: an approval is a flow with Approval nodes, and record state machines are a `state_machine` validation rule on each object. -A phantom key like `roles:` or `policies:` is **not** a silent -no-op — the top level refuses it and the stack fails to load. +A phantom key like `roles:` or `policies:` is refused at the top level. `queues` declares ownership queues (`defineQueue`, `@objectstack/spec/security`; objectstack-data): named sets of people a new record of an `ownership: 'user'` @@ -65,9 +64,8 @@ Undeclared keys are handled per surface, in two postures: been closed yet (`connectors` is one). `defineStack()` prints the warning before the parse, and the value does not survive it: `defineStack: connectors.stripe.bogusKey: 'bogusKey' is not a declared connector key, so its value is dropped at load.` - Treat these as errors-in-waiting — closing the remaining shapes is a - scheduled migration, so a key that only warns today is expected to be - refused later. + Treat these as errors-in-waiting: a key that only warns today is expected + to be refused later. Exact shape: `ObjectStackDefinitionSchema` in `node_modules/@objectstack/spec/src/stack.zod.ts`; each collection's item @@ -130,7 +128,7 @@ to a new protocol major — never to silence a mismatch. **`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 `__` 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. -**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. +**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`. The base's address serves that added locale too (both lists, 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. 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. --- @@ -183,7 +181,7 @@ The five retired remote templates (`todo`, `compliance`, `content`, ## Project Structure Conventions -Every ObjectStack project follows this directory structure: +Project layout: ``` my-app/ @@ -242,7 +240,7 @@ Under `os dev` / `os serve` / `os start` the CLI **resolves the driver itself** from the database URL and registers `DriverPlugin` for you. Do **not** put a driver in your config's `plugins:` array: no example app does, and the `plugins:` key is for plugins the CLI cannot infer (connector executors, your -own plugins). Pick a driver by setting the DB URL, not by writing code. +own plugins). Construct `DriverPlugin` yourself only when **you** own the runtime — embedding via `Runtime` / `ObjectKernel`, or a test that boots a kernel directly: From 5f2e4a8d3304fc62c94cff4a8d0934311aee091d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 00:09:04 +0000 Subject: [PATCH 4/6] docs(skills): keep the phantom-key contract sentence the top-level-keys gate pins; pay the ratchet with the template's filler bullet instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:skill-top-level-keys` pins "the top level refuses it and the stack fails to load" in skills/objectstack-platform/SKILL.md, so that sentence is restored word for word; the token the ADR-0146 sentence needs is paid by dropping the template section's "A clean slate to extend …" bullet, which states no rule. 6009 / 6012 tokens. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- .changeset/28829-pair-added-locale-served.md | 2 +- skills/objectstack-platform/SKILL.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/28829-pair-added-locale-served.md b/.changeset/28829-pair-added-locale-served.md index e11d087f815..27590fdc5ad 100644 --- a/.changeset/28829-pair-added-locale-served.md +++ b/.changeset/28829-pair-added-locale-served.md @@ -8,4 +8,4 @@ Clause-②: no Metadata-protocol: none - **Before.** The ADR-0146 customization paragraph said a locale the base does not ship is added in the pair without `overrides`, and stopped there: nothing said where that added locale is served, and nothing said what a dependent package's added locale under the base's name reaches. The reads of a base's address now decide an email template member by member, so an author could expect the pair's added locale to stay under `hotcrm.custom` only, or a dependent's to answer on the base's address. -- **Now.** One sentence beside that rule: the base's address serves the pair's added locale too (both lists, the execution list, a send in that locale), and 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. The file stays under its token ceiling: the sentence is paid for by tightening four restating clauses elsewhere in the file. No rule is removed, no frontmatter, schema or behaviour changes. +- **Now.** One sentence beside that rule: the base's address serves the pair's added locale too (both lists, the execution list, a send in that locale), and 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. The file stays under its token ceiling: the sentence is paid for by tightening four restating clauses elsewhere in the file (one intro clause, one explanatory aside, one restated driver rule, one filler template bullet). No rule is removed, no frontmatter, schema or behaviour changes. diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index 36daef8539b..ebc34d31b03 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -44,7 +44,8 @@ are `manifest`, `packages`, `objects`, `objectExtensions`, `picklists`, There is deliberately **no** top-level `workflows` or `approvals` collection: an approval is a flow with Approval nodes, and record state machines are a `state_machine` validation rule on each object. -A phantom key like `roles:` or `policies:` is refused at the top level. +A phantom key like `roles:` or `policies:` is **not** a silent +no-op — the top level refuses it and the stack fails to load. `queues` declares ownership queues (`defineQueue`, `@objectstack/spec/security`; objectstack-data): named sets of people a new record of an `ownership: 'user'` @@ -170,7 +171,6 @@ what the platform owns. `exportsOf` — the scaffolded `AGENTS.md` says how to add to them. The memory driver and the Hono server are NOT in the file — the CLI auto-registers both at boot -- A clean slate to extend with the metadata this skill describes The five retired remote templates (`todo`, `compliance`, `content`, `contracts`, `procurement`) are refused by name. From 96cb306213b011a733938c5244007469801a0319 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 01:05:19 +0000 Subject: [PATCH 5/6] docs(skills,docs): the pair-added locale sentence follows the override reads and names the layered read; the orphan rule is narrowed to a locale the base ships MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In skills/objectstack-platform/SKILL.md the sentence now sits after "…until D11." so "Then no `?package` … serve it" keeps the override as its antecedent; it names the layered read (`code` null, `effective` the pair's, as the layered read serves the pair's added member on the base's address since the member-grain layered read landed), and the orphan sentence is narrowed to an undeclared pair row at a locale the base ships. The docs page's new bullet names the layered read the same way (404 there for a dependent's) and its orphan bullet carries the same narrowing. Paid in the same file by dropping the adapter-package enumeration the glob already states, "guided by these skills" and "advanced — "; 6006 / 6012 tokens. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- content/docs/concepts/metadata-lifecycle.mdx | 15 +++++++++------ skills/objectstack-platform/SKILL.md | 9 ++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index f6c36040ade..a0d0be72edd 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -141,17 +141,20 @@ document **whole** for every read that serves the base's name member `hotcrm.custom` adds is listed by `GET /meta/email_template` with no `?package` and with `?package=hotcrm`, is in the execution list the email service reads, and so is what a send in that locale renders — stamped `_packageId: 'hotcrm.custom'`, as the - by-name read already served it. A locale another package that depends on `hotcrm` - adds under the base template's name is that package's own item, served only under - `?package=` naming it and in the list scoped to it: the read naming `hotcrm` and the read - naming no package answer `404`, and the base's lists leave it out. + by-name read already served it. The layered read naming `hotcrm` or no package agrees: + `code: null`, `overlay` and `effective` = the pair's row. A locale another package that + depends on `hotcrm` adds under the base template's name is that package's own item, + served only under `?package=` naming it and in the list scoped to it: the by-name and + layered reads naming `hotcrm` or no package answer `404`, and the base's lists leave it + out. - **The environment-wide list** serves one item per name that exactly one code package ships. A dependent package's same-named item is not listed beside it and stays under its own `?package=` address. A package-unbound row — the overlay row of the table below — still wins over the base's document until ADR-0146 D11 migrates it into the pair. -- **Orphans are reported, not dropped.** A row of the base's name stored in the pair - without the declaration, or in a package that depends on the base, is reported by the +- **Orphans are reported, not dropped.** A row of the base's name — for an `email_template`, + at a locale the base ships — stored in the pair without the declaration, or in a package + that depends on the base, is reported by the layered read in `overlay` with `effective` = the base's document. - **Export carries the declaration.** `GET /packages/hotcrm.custom/export` carries the overriding item with its `overrides` block; `GET /packages/hotcrm/export` carries the diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index ebc34d31b03..6546752c789 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -82,7 +82,7 @@ schema sits in its domain folder (`data/object.zod.ts`, `ui/view.zod.ts`, …). 2. **Cross-references** — views/actions/flows reference defined objects 3. **Seed data** — dataset objects exist in the definition -To disable (advanced — e.g., objects provided by another plugin): +To disable (e.g., objects provided by another plugin): ```typescript export default defineStack(config, { strict: false }); @@ -129,14 +129,14 @@ to a new protocol major — never to silence a mismatch. **`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 `__` 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. -**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`. The base's address serves that added locale too (both lists, 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. 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. +**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. 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 at a locale the base ships, 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. --- ## The App / Platform Boundary An ObjectStack app is a **simplified implementation of business features**: -author metadata under the platform's spec, guided by these skills, and check it +author metadata under the platform's spec and check it with the `os` commands ([Verify your work](./references/operations.md#verify-your-work)). Never rebuild what the platform owns. @@ -259,8 +259,7 @@ Two packages exist: | `@objectstack/hono` | `createHonoApp({ kernel, prefix })` | You own the server: embed ObjectStack routes in your own Hono app / deploy target. | | `@objectstack/plugin-hono-server` | `HonoServerPlugin` | ObjectStack owns the server: a kernel plugin that hosts the Hono app and opens the listening socket (this is what `os dev` / `os serve` register). | -There are **no** `@objectstack/adapter-*` packages (no adapter-express / --fastify / -nextjs / -nuxt / -nestjs / -sveltekit). To integrate another +There are **no** `@objectstack/adapter-*` packages. To integrate another framework, mount the Hono app (a web-standard `fetch` handler) or call the dispatcher yourself. From c51bbe3f5116628747397895275b3f5aec59dc68 Mon Sep 17 00:00:00 2001 From: marchtian Date: Mon, 2 Nov 2026 01:31:51 +0000 Subject: [PATCH 6/6] docs(skills): the orphan rule's locale qualifier covers a dependent's row too Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc --- skills/objectstack-platform/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index 6546752c789..fdbd68c4caa 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -129,7 +129,7 @@ to a new protocol major — never to silence a mismatch. **`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 `__` 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. -**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. 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 at a locale the base ships, 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. +**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. 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`; the base's is unchanged. ---