Repository navigation
Commit 72af58c
Fixes #21459
Clause-②: yes (narrowing)
## Summary
This PR executes ruling A (record `5964312254`). `PageSchema` now
accepts `requires` only when `kind` is `html` or `jsx`, the two kinds
the metadata save door compiles. On a `react`, `full` or `slotted` page
it is refused at parse, and so is a page that omits `kind`, which
defaults to `full`. The refusal names the key, the page's kind and the
compiled kinds.
- **The refusal: `checkPageRequiresKind`**
(`packages/spec/src/ui/page.zod.ts`). It is an exported object-level
check chained onto `PageSchema` with `.superRefine(...)`, right after
`checkPageSourceCompleteness`. That is the mechanism this file already
uses for kind-conditional rules, so no second one is added. Its
vocabulary is the new export `COMPILED_PAGE_KINDS = ['html', 'jsx']`.
The issue is `code: 'custom'` at `path: ['requires']`. The message opens
with "`requires` is refused on a `kind: 'react'` page" (or the page's
kind), names `html` and its deprecated alias `jsx`, says "Delete the
key.", and ends with the house `os migrate meta --from 17` sentence. It
carries no tracker number.
- **The describe** now says the key exists only on html / jsx pages and
is refused at parse on the other kinds. Its opening sentence, "derived
from the source at save — omit it", is unchanged byte for byte, because
the reconciliation ledger row quotes it.
- **The kit (ADR-0087):**
- D2 conversion `page-requires-non-compiled-kind-removed` (step 18,
`order: 58`, `retiredFromLoadPath: true`, `retiredAfter: '17.6.0'`). It
strips the key from `react`, `full`, `slotted` and kind-less pages.
- D3 semantic entry `page-requires-non-compiled-kind-refused`, which
carries `conversionIds: [page-requires-non-compiled-kind-removed]`.
- A hand-written `STEP18_RATIONALE` fragment (`order: 66`, placed after
`ui-ai-chat-window-retired`, which landed first at 65). The registry
regions were regenerated with `gen:migration-registry`.
- No tombstone and no `RETIRED_KEYS_BY_MAJOR` row: the key stays live on
html pages.
- **Ledgers:**
- `liveness/page.json`: the `requires` row stays `live`. Its evidence
gains the PARSE reader, its note's per-kind clause shrinks to the
compiled kinds, and `verifiedAt` is now 2026-10-03. The state counts do
not move.
- The reconciliation row in `metadata-form-zod-reconciliation.test.ts`:
its per-kind clause now reads "on every other kind the parse refuses
it".
- **Generated:** `api-surface/ui.json` and `export-origins/ui.json` gain
the two exports, and `content/docs/references/ui/page.mdx` re-renders
the describe. All three were produced by `check:generated --fix`, never
edited by hand.
- **Changeset:** `@objectstack/spec` `minor` with the BREAKING banner,
`Clause-②: yes (narrowing)`, a FROM → TO table, and the ADR-0087 marker
`registered page-requires-non-compiled-kind-removed,
page-requires-non-compiled-kind-refused`.
No runtime code changes. The save door, the load report and objectui are
untouched.
## Premise checks (at base `c98a72d69e`, re-read on the merged tree)
- **A1 census: zero producers.** Every `requires:` hit in `examples/**`,
`packages/apps/**` (none there), `content/docs/**` and `skills/**` is
the stack-level capability list. In examples that is `app-crm`,
`app-showcase` and `app-todo`'s `objectstack.config.ts`, plus prose
comments naming capability tokens. The only page bodies that carry
`requires` are the 11 `htmlPage(...)` sites in
`packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`,
all `kind: 'html'`, so they are still accepted. The proximity scan (each
`requires:` hit within 15 lines of a page marker) found them, which is
its positive control. No test pinned acceptance on a non-compiled kind.
objectui at the `.objectui-sha` pin `89cad75d55` shows page `requires`
only on stamped html fixtures, plus one compile-only type fixture
(`twins-spec-by-reference-9736.test.ts:158`). The ruling's cloud and
hotcrm census stands.
- **A2, the default kind.** An omitted `kind` parses as `full`, and the
refinement runs on the parsed value. So `{ requires: [...] }` with no
`kind` is refused as a `full` page, and the message adds "(`full` is
also the kind of a page that omits `kind`)". A `.shape` mirror without
the default reaches the check with `kind` absent and gets the same
issue, which the exports-parity fixture pins.
- **A2, the empty array.** On a non-compiled kind the save door never
looks at `[]`: `compileHtmlPage` returns `undefined`, so
`findHtmlPageSourceGaps` answers `null` and `stampHtmlPageRequires`
returns the body as written. The load report answers `[]` for it, which
reports nothing. So `[]` had no effect anywhere. The ruling's words
refuse the key ("accepts `requires` only when `kind` is `html` or
`jsx`"), not its contents, so `[]` is refused too, and the conversion
strips it too. Both are pinned.
## A4: the stored-row disposition is a mechanical drop, so it has a D2
conversion
The drop is lossless. On those kinds nothing derived the list, no
renderer read it, and the Studio page editor already drops it on every
save. Its one reader was the load report's warning. With the conversion:
- a stored row at rest replays it at every rehydration seam, loading
with one conversion notice instead of a `metadata_spec_invalid` warning
and a `_diagnostics` badge at every boot;
- an artifact built by 17.6.0 or earlier replays it too;
- an authored source is still refused at parse, because the conversion
is retired from the load path, and `os migrate meta --from 17` lists the
edit.
This is pinned at the real seam: `loadMetaFromDb` over a seeded stored
react page carrying `requires` gives `loaded: 1, errors: 0, invalid: 0`,
one notice naming the conversion, no `[page_requires_plugin_absent]`
line (the manifest lacks the plugin, so an unconverted list would have
been reported) and no `[metadata_spec_invalid]` line.
## A6: reader branches the parse boundary now makes unreachable for
non-compiled kinds (none changed here)
- `packages/metadata-protocol/src/runtime-authoring-gate.ts:616-627`,
`findPageRequiresAbsentFromManifest`, which is "Kind-agnostic on
purpose" (TSDoc at `:610`). A non-compiled page can no longer reach it:
the save door refuses the key, and at load the stored-row conversion
strips it before `reportPageRequiresAbsentAtLoad` (`protocol.ts:24418`,
called on the converted body) reads it. The kind-agnostic reach now
serves only html / jsx rows, and its TSDoc sentence is a follow-up
candidate.
- `packages/metadata-protocol/src/protocol.ts:24526`, the
`reportPageRequiresAbsentAtLoad` TSDoc "How a stored page gets here". It
is still true, but now only of html rows.
- `runtime-authoring-gate.ts:679` and `:728` were already gated to html
/ jsx by `compileHtmlPage` (`:634`). No change.
- objectui `builtinComponents.tsx` `pageSaveBody` (at the pin) deletes
`requires` on every kind. On non-compiled kinds that is now redundant,
and harmless.
No test pinned the old acceptance, so no existing test changed meaning.
## A9: merge state
PR #21531 landed as `48eb9c193f`. This branch merged `origin/main` twice
through `scripts/pm/os-regen-merge.sh`: `dafb0f6d1e` after #21531, and
`e38149267c` at `49161683fb`.
- `ui/page.zod.ts` and `migrations/registry.ts` merged textually clean.
Both rationale fragments are kept (`ui-ai-chat-window-retired` 65, this
one 66), and the regenerated regions match `check:migration-registry`.
- The three `os-regen` artifacts both sides touched were regenerated on
the merged tree, and the delta against main is exactly the two exports
and the describe row.
- Sibling entries survive, with equal counts on main and head for
`'ui-ai-chat-window-retired'` (3), `'ui/AIChatWindowProps'` (2) and
`'ai:chat_window'` (17).
- None of the 10 open PRs touches `ui/page.zod.ts`,
`migrations/registry.ts`, `conversions/registry.ts`,
`liveness/page.json` or the reconciliation test.
- Since `49161683fb`, main gained `1ac7308d7a` and `41b13331cd`. Neither
touches a file in this diff.
## Tests (all at head `e38149267c` unless noted)
- `@objectstack/spec` test (`vitest run --project local`): 604 files,
17888 passed, 1 todo. Run at `d7cd797549`; the later merge moved nothing
under `packages/spec`.
- `@objectstack/spec` typecheck: exit 0. That covers `tsc --noEmit`,
`check:scripts-typecheck` and `check:test-typecheck` (52 files / 246
errors held, unchanged).
- `@objectstack/metadata-protocol` test: 205 files passed, 3 skipped
(3157 tests passed). Typecheck exit 0, and `--listFiles` includes
`protocol.runtime-authoring-gate.test.ts`.
- New and changed pins:
- `packages/spec/src/ui/page-requires-compiled-kinds.test.ts`: the
refusal on `react`, `full` and `slotted`, checked for code, path and
named subjects; the omitted-kind default; the empty array; html / jsx
controls; no `requires` on every kind; the stack door envelope
(`STACK_SCHEMA_INVALID`, 422, at `pages.1.requires`); and the
conversion. The conversion pins cover the stored-row strip with a
notice, html / jsx kept, strip-iff-refused over the whole kind
vocabulary, unknown kind left alone, artifact replay, idempotence and
retired-from-load-path. The file also pins the ledger wiring and that
there is no tombstone, with `ui/Page:assignedProfiles` as the control.
- `object-refinement-check-exports.test.ts`: the new export is
catalogued with 8 fixtures, and the parity, bijection,
attachment-by-identifier and barrel-identity legs hold.
- `protocol.runtime-authoring-gate.test.ts`: the save door refuses
`requires` on a `react`, `full`, `slotted` or kind-less page with `{
code: 'INVALID_METADATA', status: 422 }`, one `custom` issue at
`requires`, no `jsx-*` compile finding, and nothing persisted. The html
control still saves and stamps. The load pin is described above.
- **Ablation** through `scripts/ablation-replace.mjs`, run under the
verify lock. The mutation replaces the kind condition `if
((COMPILED_PAGE_KINDS as readonly string[]).includes(kind)) return;`
with a bare `return;`, so the key is accepted on every kind again. The
source-resolved spec suites need no rebuild.
- Mutation landed: anchor 1 → 0, blob `6b13a7df8e44` → `1fd2575e28e3`,
marker on disk 1.
- Red leg: 13 failed, 174 passed. The three non-compiled-kind refusal
pins are red, along with the omitted-kind, empty-array, stack-door and
strip-iff-refused pins and the exports parity and bijection legs. The
html / jsx controls stayed green.
- Restore: blob equals HEAD (`6b13a7df8e44`) and `git diff HEAD` is
empty. Green leg: 187 passed.
## Gates
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 116 commands at `e38149267c`. All 116 were run and
their exit codes recorded. `--ran` reports: "116 derived famil(ies)
accounted for — 115 run, 1 NOT-MEASURED". The NOT-MEASURED one is `pnpm
check:dual-build-cjs-loads` (exit 3, PREREQUISITE NOT MET: it needs
every package's `dist/`, a repo-wide build), and it is declared to CI.
`check:skill-examples` and `check:lean-entry-closure` first exited 3 for
missing `client-react` / `objectql` builds. After building those
closures they re-ran to exit 0. Highlights, each exit 0:
- `check:generated` reports "all up to date" for 15 artifacts;
- `check:liveness`, `check:migration-registry`, `check:spec-changes`,
`check:upgrade-guide`, `check:api-surface`, `check:export-origins`,
`check:docs`;
- `check:adr-0087-registration`, `check-changeset-no-major` (also run
with this body as the `--event` payload), `check-empty-changeset`;
- `check:doc-authoring`, `check:nul-bytes`,
`check:cross-package-test-inputs`, `check:engine-double-contract`,
`check:type-check-debt`.
A narrowed eslint run (`--no-inline-config --format json`) covered the 8
changed `.ts` files and found 0 errors and 0 warnings. This repo's
eslint config enables no type-aware linting, so the diff cannot move a
verdict on an untouched file.
## Acceptance notes
- **objectui census tripwire.** objectui's
`spec-object-refinements-7715.test.ts` census (at the pin) counts
`PageSchema`'s object-level checks and lists `attached:
['checkPageSourceCompleteness']` for `PageNodeSchema`. It is built to go
red when the spec adds a check. At objectui's next `@objectstack/spec`
bump, that row will ask for `checkPageRequiresKind` to be attached, or
declared not attachable. Runtime behaviour needs no objectui change, as
the ruling says, but that census will need one row. The Console Pin Gate
only builds objectui, and nothing objectui compiles against changed
type, so it is unaffected.
- `examples/app-showcase` and every other example author no page-level
`requires`, so no example changed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent fd5a1cd commit 72af58c
13 files changed
Lines changed: 641 additions & 24 deletions
File tree
- .changeset
- content/docs/references/ui
- packages
- metadata-protocol/src
- spec
- api-surface
- export-origins
- liveness
- src
- conversions
- migrations
- entries/semantic
- system
- ui
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
184 | 184 | | |
185 | 185 | | |
186 | 186 | | |
187 | | - | |
| 187 | + | |
188 | 188 | | |
189 | 189 | | |
190 | 190 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1224 | 1224 | | |
1225 | 1225 | | |
1226 | 1226 | | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
1227 | 1256 | | |
1228 | 1257 | | |
1229 | 1258 | | |
| |||
1339 | 1368 | | |
1340 | 1369 | | |
1341 | 1370 | | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
1342 | 1400 | | |
1343 | 1401 | | |
1344 | 1402 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| 75 | + | |
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
| |||
482 | 483 | | |
483 | 484 | | |
484 | 485 | | |
| 486 | + | |
485 | 487 | | |
486 | 488 | | |
487 | 489 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| |||
467 | 468 | | |
468 | 469 | | |
469 | 470 | | |
| 471 | + | |
470 | 472 | | |
471 | 473 | | |
472 | 474 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
0 commit comments