Repository navigation
Commit ec7f7f8
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
- .changeset
- content/docs
- automation
- permissions
- references
- api
- system
- examples/app-showcase/src/system/emails
- packages
- lint/src
- platform-objects/src/apps/translations
- plugins/plugin-email/src
- templates
- qa
- dogfood/test/fixtures
- downstream-contract/src
- runtime/src
- spec
- api-surface
- authorable-defaults
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- liveness
- src
- conversions
- migrations
- entries
- retired-defs
- retired-keys
- semantic
- system
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | 24 | | |
26 | 25 | | |
27 | 26 | | |
| |||
56 | 55 | | |
57 | 56 | | |
58 | 57 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
63 | 66 | | |
64 | 67 | | |
65 | 68 | | |
| |||
113 | 116 | | |
114 | 117 | | |
115 | 118 | | |
116 | | - | |
117 | 119 | | |
118 | 120 | | |
119 | 121 | | |
| |||
122 | 124 | | |
123 | 125 | | |
124 | 126 | | |
125 | | - | |
126 | 127 | | |
127 | 128 | | |
128 | 129 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
53 | | - | |
| 52 | + | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
128 | | - | |
| 128 | + | |
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
| |||
371 | 371 | | |
372 | 372 | | |
373 | 373 | | |
374 | | - | |
| 374 | + | |
375 | 375 | | |
376 | 376 | | |
377 | 377 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
317 | 317 | | |
318 | 318 | | |
319 | 319 | | |
320 | | - | |
| 320 | + | |
321 | 321 | | |
322 | 322 | | |
323 | 323 | | |
| |||
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
337 | | - | |
| 337 | + | |
338 | 338 | | |
339 | 339 | | |
340 | 340 | | |
| |||
0 commit comments