diff --git a/.changeset/28829-pair-added-locale-served.md b/.changeset/28829-pair-added-locale-served.md new file mode 100644 index 00000000000..27590fdc5ad --- /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 (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/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index 3bc2be9ada2..a0d0be72edd 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -137,13 +137,24 @@ 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. 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 b7081c4c39f..fdbd68c4caa 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -65,9 +65,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 @@ -83,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 }); @@ -130,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`. 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 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. --- ## 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. @@ -172,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. @@ -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: @@ -261,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.