Skip to content

Commit ec7f7f8

Browse files
feat(spec)!: retire an email template's category and isSystem — the grouping and the built-in flag nothing read (#22876) (#22990)
Fixes #22876 Clause-②: no (narrowing) > **No longer parked.** The two artifact doors this retirement needed have both landed, and this head carries both: #22994 (`AppPlugin` registers the forward-converted copy of every collection, `4267484fe`) and #23001 (the artifact door replays step 18 for a `^18` floor, `58f5c9099`). `.objectui-sha` is at objectui `9646e2ed2bf3` (objectstack-ai/objectui#12193's merge) on `main` since #23018, and on this head. Draft until the contract-tier review on this head; then the seat queues it. ## Parked — do not land yet **Unlock condition:** this PR lands with `.objectui-sha` at or past the objectui companion's merge, objectstack-ai/objectui#12193 (triage ruling on the card: the editor change first, then the spec tombstones, in the same landing). The pin bump is its own card, filed when the companion is accepted; it queues behind the pin bump that landed with #22874. This PR stays draft, and the owning seat lands it. **Expected red:** one, and it is not this diff's. `pnpm check:platform-checklist` exits 1 with 8 findings: 7 absent `#symbol` anchors in `docs/qa/platform-checklist/areas/*.json` that name `metadata-protocol` and `service-storage` symbols, plus the symbol-anchor floor of `attachments-storage.json` (27 resolved, floor 28). The same command on `origin/main` `246edf5b3`, in a clean detached worktree, exits 1 with the identical 8 lines (`diff` of the two finding lists: identical). This diff touches none of those files. ## What this does Retires `category` and `isSystem` from the email template (`EmailTemplateDefinitionSchema`, the `email_template` metadata type), per ADR-0049 enforce-or-remove. This is the batch the seat bounded in claim `6117767270`: exactly these two keys and no other `dead` row. Neither key has had a reader since ADR-0131 D6/D10 retired the projection of declared templates into `sys_email_template` rows (`category` fed a Setup column; `isSystem` fed the built-in seeder's stand-down). Both carried a schema default, so every parsed template carried them, and the dead-property advisory fired on every template with no way for an author to clear it. ## The retirement kit (stage 0's table, surface by surface) - **Tombstones.** `retiredKey()` on both keys in the `strictObject` (`packages/spec/src/system/email-template.zod.ts`). Each prescription opens with the fully qualified key and closes with the house `os migrate meta --from 17` sentence. The orphan enum `EmailTemplateDefinitionCategorySchema` and its type leave with the key: its only other consumer was the type-alias pin. - **Form.** `email-template.form.ts` drops the `category` select and the `isSystem` checkbox. This is the form Studio renders. - **D2 conversion `email-template-category-is-system-removed`.** Protocol 18, `retiredFromLoadPath`, `retiredAfter: '17.7.0'`, order 62. It inserts between `elementTextVariantHeadingLevels` and `fieldColumnListsCanonicalized` by identifier, and is defined directly above the latter. It strips both keys from every `emailTemplates[]` entry, one notice per key, and names the template in the path. Its fixture has 4 notices: the built-in shape (both keys authored), a 17.x-emitted shape (both defaults materialized) and an untouched canonical control. - **Registration.** `RETIRED_KEYS_BY_MAJOR[18]` gains `system/EmailTemplateDefinition:category` and `system/EmailTemplateDefinition:isSystem`. `RETIRED_DEFS_BY_MAJOR[18]` gains `system/EmailTemplateDefinitionCategory`, and its `json-schema.manifest/system.json` key is deleted on purpose: the build named it as a disappeared schema. The D3 entry `email-template-category-is-system-retired` comes with its `STEP18_RATIONALE` fragment (order 94). - **Ledger.** Both rows stay `dead` and are re-noted as tombstones (`verifiedAt` 2026-10-12), with the `_note` and the README row updated. The dead count is unchanged, so the state-counts shard does not move (`check:liveness` green). - **Generated.** `authorable-surface` (two `[RETIRED]` rows), `authorable-defaults` (the two defaults leave with their keys), `api-surface`, `export-origins`, `declaration-map`, three reference pages, and the four `metadata-forms.generated.ts` bundles (a pure deletion of the two form labels). - **Built-ins and fixtures.** `plugin-email`'s six built-in auth templates drop `category: 'auth'` and `isSystem: true` (12 lines). The same goes for the showcase template, the ADR-0054 dogfood fixture, the downstream-contract fixture and two `plugin-email` test fixtures. - **Lint pins re-judged.** `lint-liveness-properties.test.ts` now pins that a parsed template carries neither key and lints clean, and that the parse refuses an authored one. `runtime-gate.inert-type-writes.test.ts` returns to a silent `email_template` write and asserts the door's schema refuses `category`. The showcase control is silent again. - **Docs.** `automation/email-templates.mdx` (two examples and the `category` paragraph) and `permissions/system-context.mdx` with its census row prose: the `EmailTemplate.isSystem` declaration is now a tombstone. - **Changeset.** `@objectstack/spec` `minor` with the BREAKING banner, the FROM → TO table, the one-line fix and one ADR-0087 disposition (`registered`). It also names `@objectstack/plugin-email` and `@objectstack/platform-objects` as `patch`, and carries the `Clause-②` line. ## Deviations from stage 0's kit and the claimed file surface (named, measured) - **Two count pins outside the listed surface, each moved only because this retirement removes what it counts:** `packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts` (the metadata-form catalog loses the two labels; it reads 668 after merging #22943's +1) and `type-alias-convention.pin.test.ts` (775 → 774, the orphan enum's isomorphic pin and its module import). - **The retired-default residue stage (`acceptRetiredDefaultResidue`) is NOT adopted.** Stage 0 did not raise it, and the decision is recorded in both retired-key entry files. Both keys were defaulted, so a 17.x parse materialized them into every template it emitted. I followed the `ai/Agent:memory.longTerm.store` precedent: the replayed D2 conversion heals built artifacts (the artifact door) and stored rows (`convertStoredItem`, which every served and execution read runs), and the card's "Done when" requires the parse to refuse both keys. Residue would accept the default values at parse. The two doors that skipped the replay were measured on #22876 (seat option C) and are fixed on `main` by #22994 and #23001; this head carries both. ## Studio save, measured at the door Studio calls Booted own backend: `pnpm dev -- --fresh -p 38876` (showcase, built-in templates registered by `EmailServicePlugin`), signed in as the seeded admin, then killed by recorded PID and port. - `GET /api/v1/meta/email_template` returned 25 templates (the showcase's and the 6 built-ins × 4 locales). None carries `category` or `isSystem`. - `GET /api/v1/meta/types`: the served `email_template` schema has 19 properties and neither key. The served form's fields are `name`, `label`, `locale`, `description`, `subject`, `bodyHtml`, `bodyText`, `variables`, `fromOverride`, `replyTo` and `active`. - `PUT /api/v1/meta/email_template/auth.password_reset?mode=draft` is the door `client.meta.saveItem` uses for Studio's draft save. Each body was the served built-in minus the `_`-prefixed read decorations, with a new subject: - (a) neither key → **200**, `success: true`, "Saved customization overlay (env-wide, state=draft)". - (b) plus `category: 'auth'` → **422**, `code: INVALID_METADATA`, one issue at `category` (`invalid_type`) carrying the prescription. - (c) plus `isSystem: true` → **422**, `INVALID_METADATA`, one issue at `isSystem` with its prescription. - (d) plus the 17.x defaults `category: 'custom'`, `isSystem: false` → **422** with both issues. With no residue stage, a client body carrying the materialized defaults is refused too. ## Merge round (head `1406d0ee7`) - **Merges, both through `scripts/pm/os-regen-merge.sh`.** `b71a2d817` merged `origin/main` `0ede0f8ab`, which carries door 1 (#23023, `58f5c9099`). Its one hand conflict was `packages/spec/src/type-alias-convention.pin.test.ts`, the isomorphic-pin count: `main` added `InstallModeSchema` (775 → 776) and this branch removes the category enum's pin (776 → 775). The pin's own counter (the `export type Iso…` declarations it matches at line start) counts 775 on the merged file, and both history notes are kept. The driver took `main`'s side of `content/docs/references/index.mdx` and `content/docs/references/api/package-api-assembled.mdx`; `432845a82` regenerates both from the merged tree (`gen:schema`, then `gen:docs`). `c116f7607` merged `origin/main` `4267484fe`, door 2 (#23024), with no conflict. - **One merge consequence, fixed in `1406d0ee7`.** Door 2's new `packages/runtime/src/app-plugin-converted-collections.test.ts` modelled a 17.x-emitted email template with `category: 'custom'` and `isSystem: false`, and this PR's tree-scoped absence pin refused it (CI `Test Core (1/6)` on `c116f7607`). The fixture now authors neither key. As merged, with both keys present, that file passed 8 of 8, so door 2 plus this PR's D2 conversion strip both keys on the artifact boot. Its assertions do not depend on the keys; its dashboard legs carry the conversion proof. The pin's exclusions are unchanged. - **Net diff unchanged except for the merge.** `git diff BASE HEAD | git patch-id --stable` gives `2db65c91c0` before (base `246edf5b3`, head `e4e96505e`) and `0372033243` after (base `4267484fe`, head `1406d0ee7`). Excluding the type-alias pin, the runtime fixture and the generated reference pages, it is `74063f68a2` on both sides, and all 35 of those files have byte-identical per-file patch-ids. The diff is now 1,283 changed lines over 40 files. - **Evidence at `1406d0ee7`.** All exit 0 unless noted: - `check:generated` (all 14 artifacts current) and `check-i18n-bundles` (in sync). - `@objectstack/spec` local project in two shards: 653 files, 19,500 tests (1 todo). Repo project: 56 files, 909 tests, including the 24-test retirement pin. - `@objectstack/lint` 137 / 6,569; `@objectstack/platform-objects` 70 / 1,096; `@objectstack/plugin-email` 33 / 515; the runtime fixture file 8 / 8. - `typecheck` for runtime and spec. - `dispatch-gates --commands` derived 137; 136 exit 0. `pnpm check:platform-checklist` exits 1 with the 8 findings it also gives on `4267484fe` (identical lists). `--ran`: 137 derived, 137 run, 0 NOT-MEASURED. - CI on `1406d0ee7`: 41 check runs, 36 success, 5 skipped, `Test Core` and all six shards green. ## Evidence (final head `e4e96505e`, after merging `origin/main` twice: `e5687cb6b` with #22943, then `246edf5b3` with #22874) Both merges went through `scripts/pm/os-regen-merge.sh`. Conflicts were settled by stacking both intents: the catalog count pin (#22943 +1, this −2) and the adjacent `job` row in `liveness/README.md`. `references/system/email-template.mdx` was regenerated over the merged tree with `gen:schema` + `gen:docs`, so it carries #22874's header sentence and the two retired rows. The four bundles were regenerated with `node scripts/check-i18n-bundles.mjs --write` after the first merge (no diff against the text merge). - `pnpm --filter @objectstack/spec build`, then `check:generated`: all 14 artifacts up to date, exit 0. - `@objectstack/spec` local project, in two shards: 650 files, 19,416 tests passed (1 todo), exit 0. - `@objectstack/spec` repo project: 55 of 56 files, 907 tests passed, exit 0. NOT MEASURED: `scripts/build-schemas-check-mode.test.ts`, which alone exceeds the foreground cap (the precedent's reading); the gate whose mode it tests, `check:authorable-surface`, is green. - The new pin `src/system/email-template-category-is-system-retirement.test.ts` (repo project; listed in `vitest.repo-tests.json`): 24 tests. - Package tests, all exit 0: `@objectstack/lint` 137 files / 6,529 tests; `@objectstack/platform-objects` 69 / 1,083; `@objectstack/plugin-email` 33 / 515; `@objectstack/example-showcase` 33 / 409; `@objectstack/downstream-contract` 3 / 31; `@objectstack/dogfood` (the three email-template files plus `expression-conformance`) 4 / 16. - `typecheck`, all exit 0: spec (including `check:test-typecheck`, which compiles the pin's two `@ts-expect-error` lines), lint, platform-objects, plugin-email, example-showcase, dogfood, downstream-contract. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) derived 137 commands at this head. 136 exited 0. One exited 1: `pnpm check:platform-checklist`, the pre-existing red named under "Expected red". `origin/main` has since moved three commits (`a2240c4dc`), and none of them touches a file in this diff; the seat re-merges at landing. `--ran` (each line recorded as `command :: exit code`): 137 derived, 137 run, 0 NOT-MEASURED, 0 UNRUN. - ESLint, as a proven narrowing rather than the repo-wide `pnpm lint`. ① Population: `eslint.config.mjs` lints `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`. ② `--no-inline-config --format json` over the 23 changed lintable files at `e4e96505e`: 23 files, 0 errors, 0 warnings, none ignored. ③ Invariance: the config never enables type-aware linting (no `parserOptions.project`, no `projectService`), and the diff touches neither the config nor a baseline, so no verdict on an untouched file can move. - Changed lines: 1,260 (+1,082 / −178, 39 files), under the 3,000 human-merge line. No governed surface is touched. ## Ablations (one-shot, through `scripts/ablation-replace.mjs`, each wrapped in a trap and restored by blob equal to HEAD with an empty `git diff HEAD`; spec-source imports, no `dist/` involved) - `category: retiredKey(EMAIL_TEMPLATE_CATEGORY_REMOVED)` replaced by `category: z.string().optional()` (anchor 1 → 0): 10 of the pin's 24 tests went red (the refusal, every-value, both-keys, write-door, `defineEmailTemplateDefinition`, `defineStack` envelope, did-you-mean, tsc-channel, boot-door and tree-walk rows), and 14 stayed green. - `category: 'auth'` planted on `AUTH_PASSWORD_RESET_TEMPLATE` (anchor 1 → 0): exactly the tree-scoped walk went red, naming `packages/plugins/plugin-email/src/templates/auth-templates.ts: category beside bodyHtml`. A first attempt whose replacement still contained the anchor was refused by the tool as a no-op and re-run with a landing replacement. ## New pin `packages/spec/src/system/email-template-category-is-system-retirement.test.ts`: - **The tombstones at every door:** the schema, the `email_template` registry binding, `defineEmailTemplateDefinition`, and `defineStack`, which refuses with `STACK_SCHEMA_INVALID` / 422. Every value is refused, the old defaults included. Also pinned: the did-you-mean, the walked shape, the tsc channel, and the enum gone from `@objectstack/spec/system`. - **The D2 strip:** the stored-row seam, the boot door before and after the strip, a lit control, every value shape, idempotence, and retirement from the load path. - **The registrations.** - **A tree-scoped absence walk** over the radius `@objectstack/spec#test` already declares (`check:cross-package-test-inputs` green). It judges either key at the top level of an object literal that also carries `bodyHtml`, after masking strings and comments, plus YAML mappings and MD/MDX fences by language. ## Acceptance notes - **Bound kept:** no other `dead` row, not `realtime_subscription`, not the `sys_email_template` object's own `category` / `is_system` columns, not `.objectui-sha`, no `skills/**`, `.claude/**` or `docs/adr/**`. - **Not edited (outside the claimed surface), now stale:** `docs/qa/platform-checklist/areas/integration-system.json` describes the showcase fixture as "category workflow". This is a one-line prose fix for whoever touches that file next. - **Overlap:** #22874 landed before this PR opened and was merged in; #22943 too. - **The out-of-repo population is NOT MEASURED,** including cloud, which the card's measurement also left open. - The objectui companion, objectstack-ai/objectui#12193, drops the same two inputs from Studio's fallback schema. It is hygiene: a current server serves its own schema and form, measured above. --- _Generated by [Claude Code](https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0cbfb7c commit ec7f7f8

40 files changed

Lines changed: 1096 additions & 187 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-email': patch
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
feat(spec)!: retire an email template's `category` and `isSystem` — the grouping and the built-in flag nothing read (#22876)
8+
9+
**BREAKING** — `category` and `isSystem` on an email template (`EmailTemplateDefinitionSchema`, the `email_template` metadata type) are now refused at parse. Nothing read either key once templates stopped being copied into `sys_email_template` rows: the Studio listing never offered the filter facet `category` promised, and no door refused deleting a template that set `isSystem`. Delete both keys. A template is found by its `name`, so group templates with a dotted namespace prefix (`auth.password_reset`, `crm.welcome`). A built-in template is a code package's declaration: a Studio save overrides it for the environment, and discarding that save restores the package's copy.
10+
11+
Clause-②: no (narrowing)
12+
13+
Measured before removal: no reader of either key in `@objectstack/plugin-email`, `@objectstack/service-messaging`, `@objectstack/plugin-auth`, the metadata protocol, ObjectQL, the runtime or the REST layer, and none in the Studio console at its pinned commit beyond a fallback form input, which the console's own companion change removes. Both keys carried a schema default (`category: 'custom'`, `isSystem: false`), so every parsed template carried them and the liveness advisory fired on every template, with no way for an author to clear it.
14+
15+
## FROM → TO
16+
17+
| you wrote (17.7 and earlier) | write instead |
18+
| --- | --- |
19+
| `category: 'auth'` (or `'notification'`, `'workflow'`, `'marketing'`, `'custom'`) | nothing — delete the key; group by the `name` prefix |
20+
| `isSystem: true` | nothing — delete the key |
21+
| `isSystem: false` | nothing — delete the key |
22+
| `import { EmailTemplateDefinitionCategorySchema } from '@objectstack/spec/system'` | nothing — the enum is removed with its key |
23+
| `type EmailTemplateDefinitionCategory` | nothing — the type is removed with its key |
24+
25+
**The one-line fix:** delete `category` and `isSystem` from every email template.
26+
27+
**What an author who still writes either sees.** `tsc` fails at the authoring site (`EmailTemplateDefinition` types both keys as retired), and the parse — `defineEmailTemplateDefinition()`, `defineStack({ emailTemplates })`, `PUT /api/v1/meta/email_template/:name` — refuses each at its own path with the prescription:
28+
29+
> `email_template.category` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing read it: the Studio listing never offered the filter facet it promised, and delivery never depended on it. Delete the key. A template is found by its `name`, so group templates with a dotted namespace prefix instead (`auth.password_reset`, `crm.welcome`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
30+
31+
> `email_template.isSystem` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing enforced it: no door refused deleting a template that set it. Delete the key. A built-in template is a code package's declaration: a Studio save overrides it for the environment, and discarding that save restores the package's copy. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
32+
33+
## The retirement kit
34+
35+
- **Two `retiredKey()` tombstones** on `EmailTemplateDefinitionSchema`, a `strictObject` — so the refusal carries the prescription rather than a bare unknown-key report, and `tsc` fails first. `RETIRED_KEYS_BY_MAJOR[18]`: `system/EmailTemplateDefinition:category`, `system/EmailTemplateDefinition:isSystem`. The `category` enum, `EmailTemplateDefinitionCategorySchema`, had no other consumer and leaves with it: `RETIRED_DEFS_BY_MAJOR[18]`: `system/EmailTemplateDefinitionCategory`.
36+
- **The D2 conversion `email-template-category-is-system-removed`** (protocol 18, retired from the load path) deletes both keys from every `emailTemplates[]` entry, one notice per key, as a lossless delete. Because both keys carried a default, every built artifact and every stored `email_template` row written under 17.x carries them; both load through the rehydration seams, which replay this conversion. The retired-default residue stage is not adopted: the replayed conversion heals those populations, and an authored source that still writes either key meets the prescription.
37+
- **The D3 entry `email-template-category-is-system-retired`** asks the author whether anything they built read either key — a report or integration that filtered templates by `category`, or one that treated `isSystem: true` as protection against deletion. Nothing ever enforced that.
38+
- **Studio:** the email-template form drops the `category` select and the `isSystem` checkbox.
39+
- **Ledger:** both rows stay `dead`, re-noted as tombstones (a tombstone keeps the key in the walked shape).
40+
- **No deprecation window**, per the project's startup-stage posture.
41+
42+
**`@objectstack/plugin-email`:** the six built-in auth templates (in every locale) no longer write `category: 'auth'` or `isSystem: true`. They render and send exactly as before.
43+
44+
**`@objectstack/platform-objects`:** the metadata-form translation bundles drop the two retired form inputs' labels (`email_template.fields.category`, `email_template.fields.isSystem`). The `sys_email_template` object and its `category` / `is_system` columns are unchanged.
45+
46+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for.
47+
48+
<!-- adr-0087: registered email-template-category-is-system-removed, email-template-category-is-system-retired -->

‎content/docs/automation/email-templates.mdx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { defineEmailTemplateDefinition } from '@objectstack/spec';
2121
export const TaskDoneEmail = defineEmailTemplateDefinition({
2222
name: 'crm.task_done',
2323
label: 'Task Done Notification',
24-
category: 'workflow',
2524
locale: 'en-US',
2625
subject: 'Task done: {{task.title}}',
2726
bodyHtml: '<p>The task <strong>{{task.title}}</strong> on {{project.name}} was marked done.</p>',
@@ -56,10 +55,14 @@ enforces dotted snake_case (`auth.password_reset`, `crm.large_deal_won`).
5655
Prefix it with your app or domain — the namespace is what keeps a tenant's
5756
templates from colliding with the built-in authentication mail.
5857

59-
`category` (`auth` | `notification` | `workflow` | `marketing` | `custom`,
60-
default `custom`) is a filter facet in Studio listings, not a delivery
61-
behaviour. `active: false` makes `sendTemplate` return `TEMPLATE_INACTIVE`
62-
rather than silently sending nothing.
58+
The namespace is also how you group templates: there is no separate category
59+
key. `category` and `isSystem` were removed (ADR-0049 enforce-or-remove) because
60+
nothing read either one, and the schema now refuses both with a migration note.
61+
A built-in template is the email plugin's declaration, which a Studio save
62+
overrides for the environment and discarding that save restores.
63+
64+
`active: false` makes `sendTemplate` return `TEMPLATE_INACTIVE` rather than
65+
silently sending nothing.
6366

6467
## Placeholders
6568

@@ -113,7 +116,6 @@ import { defineEmailTemplateDefinition } from '@objectstack/spec';
113116
export const passwordResetEn = defineEmailTemplateDefinition({
114117
name: 'auth.password_reset',
115118
label: 'Password Reset',
116-
category: 'auth',
117119
locale: 'en-US',
118120
subject: 'Reset your password',
119121
bodyHtml: '<p>Use {{{reset_url}}} within {{ttl_minutes}} minutes.</p>',
@@ -122,7 +124,6 @@ export const passwordResetEn = defineEmailTemplateDefinition({
122124
export const passwordResetZh = defineEmailTemplateDefinition({
123125
name: 'auth.password_reset',
124126
label: 'Password Reset',
125-
category: 'auth',
126127
locale: 'zh-CN',
127128
subject: '重置您的密码',
128129
bodyHtml: '<p>请在 {{ttl_minutes}} 分钟内使用 {{{reset_url}}}。</p>',

‎content/docs/permissions/system-context.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ below, and it is left open deliberately rather than hidden — see
4949
## Which `isSystem` this page is about
5050

5151
Three unrelated declarations share the identifier. **This page documents only the
52-
first.** The others are ordinary metadata fields on a stored document and have
53-
nothing to do with elevation. (A fourth, `Environment.isSystem` — a
52+
first.** The others are metadata keys on a stored document (one of them now
53+
retired) and have nothing to do with elevation. (A fourth, `Environment.isSystem` — a
5454
platform-infrastructure environment, not user data — left this repo with the
5555
`@objectstack/spec/cloud` subpath in #16325 and is declared in the cloud repo.)
5656

5757
| Declaration | What it is | This page? |
5858
|:---|:---|:---:|
5959
| `ExecutionContext.isSystem` — `packages/spec/src/kernel/execution-context.zod.ts#isSystem` | The elevation flag on an operation's context | ✅ |
6060
| `Object.isSystem` — `packages/spec/src/data/object.zod.ts#isSystem` | Marks a **system object** (protected from deletion; defaults its org-wide sharing to `public` when no `sharingModel` is set) | ❌ |
61-
| `EmailTemplate.isSystem` — `packages/spec/src/system/email-template.zod.ts#isSystem` | Built-in template; tenants may override but should not delete | ❌ |
61+
| `EmailTemplate.isSystem` — `packages/spec/src/system/email-template.zod.ts#isSystem` | A retired key: a `retiredKey()` tombstone, which the parse refuses with a migration note | ❌ |
6262

6363
The collision is a genuine hazard rather than a naming nit: `Object.isSystem`
6464
changes an object's **default sharing**, and `ExecutionContext.isSystem` changes

‎content/docs/references/api/package-api-assembled.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ Installed package row whose manifest is the assembled package body
125125
| **actions** | `{ name: string; label: string \| Record<string, string>; description?: string \| Record<string, string>; objectName?: string; … }[]` | optional | Global and Object Actions. Unique per scope, not per stack: the runtime keys every action by its owning object's name (or 'global' when object-less), a colon, then the action name, and defineStack refuses two declarations that resolve to one key — both here, both on one object's actions, or one in each position, identical twins included (an embedded action is keyed by the object it is written on, not by its own objectName). One global and one object-bound action may share a name; on that object's route the object's own actions take precedence for by-name readers. composeStacks runs the same key rule across its input stacks (counting distinct stacks, not sites) and names both source stacks on a collision. |
126126
| **flows** | `{ name: string; label: string; description?: string; successMessage?: string; … }[]` | optional | Screen Flows |
127127
| **jobs** | `{ name: string; label?: string; description?: string; schedule: object \| object \| object; … }[]` | optional | Background / Scheduled Jobs (run by IJobService on cron/interval/once schedules) |
128-
| **emailTemplates** | `{ name: string; label: string; category?: Enum<'auth' \| 'notification' \| 'workflow' \| 'marketing' \| 'custom'>; locale?: string; … }[]` | optional | Email Templates resolved by IEmailService.sendTemplate(`{ template, locale }`) |
128+
| **emailTemplates** | `{ name: string; label: string; locale?: string; subject: string; … }[]` | optional | Email Templates resolved by IEmailService.sendTemplate(`{ template, locale }`) |
129129
| **docs** | `{ name: string; label?: string; description?: string; content: string; … }[]` | optional | Package documentation — flat Markdown items compiled from src/docs/*.md (ADR-0046) |
130130
| **books** | `{ name: string; label?: string; description?: string; slug?: string; … }[]` | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
131131
| **positions** | `{ name: string; label: string; description?: string; permissionSets?: string[]; … }[]` | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |
@@ -371,7 +371,7 @@ Installed package row whose manifest is the assembled package body
371371
| **actions** | `{ name: string; label: string \| Record<string, string>; description?: string \| Record<string, string>; objectName?: string; … }[]` | optional | Global and Object Actions. Unique per scope, not per stack: the runtime keys every action by its owning object's name (or 'global' when object-less), a colon, then the action name, and defineStack refuses two declarations that resolve to one key — both here, both on one object's actions, or one in each position, identical twins included (an embedded action is keyed by the object it is written on, not by its own objectName). One global and one object-bound action may share a name; on that object's route the object's own actions take precedence for by-name readers. composeStacks runs the same key rule across its input stacks (counting distinct stacks, not sites) and names both source stacks on a collision. |
372372
| **flows** | `{ name: string; label: string; description?: string; successMessage?: string; … }[]` | optional | Screen Flows |
373373
| **jobs** | `{ name: string; label?: string; description?: string; schedule: object \| object \| object; … }[]` | optional | Background / Scheduled Jobs (run by IJobService on cron/interval/once schedules) |
374-
| **emailTemplates** | `{ name: string; label: string; category?: Enum<'auth' \| 'notification' \| 'workflow' \| 'marketing' \| 'custom'>; locale?: string; … }[]` | optional | Email Templates resolved by IEmailService.sendTemplate(`{ template, locale }`) |
374+
| **emailTemplates** | `{ name: string; label: string; locale?: string; subject: string; … }[]` | optional | Email Templates resolved by IEmailService.sendTemplate(`{ template, locale }`) |
375375
| **docs** | `{ name: string; label?: string; description?: string; content: string; … }[]` | optional | Package documentation — flat Markdown items compiled from src/docs/*.md (ADR-0046) |
376376
| **books** | `{ name: string; label?: string; description?: string; slug?: string; … }[]` | optional | Documentation navigation spines — ordered groups with derived membership (ADR-0046 §6) |
377377
| **positions** | `{ name: string; label: string; description?: string; permissionSets?: string[]; … }[]` | optional | Positions — flat capability-distribution groups (ADR-0090 D3) |

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1524 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1523 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -32,9 +32,9 @@ counts are sums of the rows they head. Regenerate with
3232
| [Security Protocol](/docs/references/security) | 5 | 30 | Permission sets, row-level security, sharing rules, tenancy posture. |
3333
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
35-
| [System Protocol](/docs/references/system) | 35 | 278 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
35+
| [System Protocol](/docs/references/system) | 35 | 277 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 166 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **196** | **1524** | 14 protocol modules |
37+
| **Total** | **196** | **1523** | 14 protocol modules |
3838

3939
---
4040

@@ -317,7 +317,7 @@ Studio designer metadata — the authoring surfaces for the protocols above.
317317

318318
## System Protocol
319319

320-
**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **35 pages, 278 schemas**
320+
**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **35 pages, 277 schemas**
321321

322322
The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance.
323323

@@ -334,7 +334,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
334334
| [`disaster-recovery.zod.ts`](/docs/references/system/disaster-recovery) | `BackupConfig`, `BackupRetention`, `BackupStrategy`, `DisasterRecoveryPlan`, `FailoverConfig`, `FailoverMode`, `RPO`, `RTO` |
335335
| [`doc.zod.ts`](/docs/references/system/doc) | `Doc` |
336336
| [`email-config.zod.ts`](/docs/references/system/email-config) | `EmailAddressConfig`, `EmailProvider`, `EmailServiceConfig`, `StackEmailConfig`, `StackEmailProviderOptions` |
337-
| [`email-template.zod.ts`](/docs/references/system/email-template) | `EmailTemplateDefinition`, `EmailTemplateDefinitionCategory`, `EmailTemplateDefinitionVariable` |
337+
| [`email-template.zod.ts`](/docs/references/system/email-template) | `EmailTemplateDefinition`, `EmailTemplateDefinitionVariable` |
338338
| [`encryption.zod.ts`](/docs/references/system/encryption) | `EncryptionAlgorithm`, `EncryptionConfig`, `FieldEncryption`, `KeyManagementProvider`, `KeyRotationPolicy` |
339339
| [`environment-artifact.zod.ts`](/docs/references/system/environment-artifact) | `Sha256Digest` |
340340
| [`http-server.zod.ts`](/docs/references/system/http-server) | `MiddlewareConfig`, `MiddlewareType`, `RouteHandlerMetadata` |

0 commit comments

Comments
 (0)