Skip to content

Commit ccccdcc

Browse files
feat(spec): doc navigation item targeting a book and/or a doc (#19789)
Fixes #19482 Clause-②: yes The spec half of the ruling on #19482 (letter A, with the book/doc addendum): one `doc` navigation item variant that opens a `book` and/or a `doc`, with at least one required. The objectui half (renderer + editor) is objectui#10188 and is not touched here. ## What changed - **`DocNavItemSchema`** (`packages/spec/src/ui/app.zod.ts`) sits with the other nav variants and is strict in the same way (`strictObject(navItemSurface('doc'), …)`). It is a member of the `NavigationItemSchema` discriminated union and of both hand-tied types (`NavigationItem`, `NavigationItemInput`). New exports: `DocNavItemSchema`, `DocNavItem`, `DocNavItemParsed`. - `book?`: a declared book name, or the package id for the package's implicit book. `doc?`: a doc name (snake_case filename stem, so `crm_lead_guide.md` is refused). - `.refine(requiredOneOf(['book', 'doc']))`: an item with neither is refused, and the message names both keys. - The `.describe()` states the audience rule from the addendum. A `book` entry shows the member's pruned subset of pages and is hidden from a member with no readable page. A `doc` entry the member may not read is hidden. `visible` / `requiredPermissions` can only narrow further. - Near-miss answers: `docName` → `doc` and `bookName` → `book`. `book` / `doc` written on another variant points at `type: 'doc'`, and the two prose targets are added to `PROSE_ALIAS_TARGETS`. - **The "names no existing book/doc in the package" refusal is new rule `docs/nav-target`** in `packages/cli/src/utils/collect-docs.ts` (`lintDocNavTargets`, called from `collectAndLintDocs`, so it runs in `os build`, `os validate` and `os lint`). It refuses with a did-you-mean and a remedy. It walks app `navigation`, `areas[].navigation` (children included) and `manifest.navigationContributions` items. - Docs: `content/docs/ui/apps.mdx` gains a Doc Navigation section, and the `app navigation item` row in `packages/spec/variant-docs.json` is re-keyed to include `doc` (`check:variant-docs`). `skills/objectstack-ui/SKILL.md` gains a `doc` row, which `check:skill-identifier-liveness` requires (see "Governed surface" below). - Regenerated with the repo's generators (`check:generated --fix`, never by hand): `authorable-surface/ui.json`, `api-surface/ui.json`, `export-origins/ui.json`, `declaration-map/ui.json`, `json-schema.manifest/ui.json`, `content/docs/references/**`, and the strictness-ledger counts. - Changeset `.changeset/19482-doc-nav-item.md`: `@objectstack/spec` minor, `@objectstack/cli` minor. ## Landing site of the reference check, and why it is not `validateCrossReferences` The dispatch assumed the check would go beside the sibling nav-target checks (`objectName` / `dashboardName` / `pageName` / `reportName`) in `validateCrossReferences`, `packages/spec/src/stack.zod.ts`. I measured that site and it cannot see docs. Docs join the artifact from `src/docs/*.md` in `collectAndLintDocs`, after the config module (and so `defineStack`) has already run. For example, `examples/app-showcase` authors `books` inline in `defineStack` but has no inline `docs`: its nine docs live in `src/docs/`. A doc arm in `validateCrossReferences` would therefore do one of two wrong things. Size-gated like its siblings, it would never fire. Ungated, it would refuse a doc that the build is about to collect. The CLI docs step is the one place that holds the complete doc set (inline docs, docs read off disk, and every package's docs). It already hosts the reverse-direction check (`docs/metadata-embed-ref`). Both targets of one item are judged there, so the rule has a single site. ## JSON Schema: refine, not a union — the PM assumption is falsified The "at least one of" rule is written as `.refine(requiredOneOf(['book', 'doc']))`. `requiredOneOf` is one of the closed, projectable refinements from #18670 item 2 (`src/shared/refinement-projection.ts`), so the generator publishes it. Measured: `json-schema/ui/DocNavItem.json` carries an `allOf` of `anyOf: [{required:[book]}, {required:[doc]}]`, and the new test pins the projection. With this, the published schema is no wider than zod. A union of three shapes would have given the same JSON Schema. It would also have replaced the single remedy message with an `invalid_union` aggregate, and it would have split the per-variant strict surface into three. The `allOf` entry appears twice. That is a pre-existing generator quirk: `automation/AssignmentExpressionValue.json` does the same. The two entries are identical, so the result is unchanged. See the Acceptance notes. ## The ruling's three confidence gaps — first readings 1. **The `book` runtime overlay in objectui is not measurable from this repo.** There is no objectui checkout in this container (`.objectui-sha` = `87af769e`). Backend side, measured: the `book` registry row is `allowOrgOverride: false` (`packages/spec/src/kernel/metadata-plugin.zod.ts:1080`). That value was rolled back from ADR-0046 §6.4's `true` (#6483). A Studio drag-edit of a PACKAGED book answers `403 not_overridable`, and new runtime books still work (`allowRuntimeCreate: true`). 2. **`/meta/doc` content limits, measured: none at the schema, and none on the save path.** `DocSchema.content` is an unbounded `z.string()` (`packages/spec/src/system/doc.zod.ts`). The MDX and image bans run only in the build lint (`lintDocs` in `collect-docs.ts`), not on `PUT /meta/doc`. A grep for a body limit (`bodyLimit`, `maxBody`, `PAYLOAD_TOO_LARGE`) finds none on the metadata routes; the only hit is `packages/rest/src/import-prepare.ts`. 3. **How the docs audience gate applies to a nav entry, measured: the content is gated server-side, the entry is not.** `/meta/doc`, `/meta/book` and `/meta/book/:name/tree` apply the ADR-0046 §6.7 audience gate (`packages/rest/src/rest-server.ts`, the book tree route and the list gates). The server-side app-nav filter (`filterAppForUserWithReason`) prunes only on `requiredPermissions`, `requiresService` and object servability. It has no audience arm. So the server still sends a `doc` entry to a member who may not read the doc, and hiding it is up to the renderer (objectui#10188). Clicking it hits the gated read (401/403). This PR does not add an audience arm to that filter. That question is raised in the report for the seat. ## Governed surface: this PR is Tier H `skills/objectstack-ui/SKILL.md` is a `skills/**` path, which makes the whole PR Tier H. `check:skill-identifier-liveness` holds the Navigation Item Types table exhaustive over `NavItemVariant`. Adding the variant without its row reds that gate, and the other remedy (`--update` of the gap ledger) is maintainer-only. Readings: | Measure | Before | After | |:--|--:|--:| | `skills/objectstack-ui/SKILL.md` lines | 309 | 310 | | All `skills/*/SKILL.md` lines | 4401 | 4402 | | `skills/objectstack-ui/SKILL.md` tokens (ceiling 3856) | ≤ 3856 | 3854 | The new row's tokens are paid for by deleting text in the same file: - The object row's `Target precedence: recordId → filters → viewName` was wrong. The spec refuses `filters` with `recordId` or `viewName` and states no precedence (`objectNavTargetExclusivity`). - The page row's `(type: 'home' | 'list' | ...)` aside is gone; page types are covered in `rules/pages.md`. - The component row's `params` become props clause is gone. ## Tests (at `f9baf5cb3f`) - `pnpm --filter @objectstack/spec test`: 517 files, 15077 passed, 1 todo. `pnpm --filter @objectstack/spec typecheck`: exit 0 (`check:test-typecheck: OK`). - `pnpm --filter @objectstack/cli exec vitest run --project unit` over `collect-docs.nav-target.test.ts`, `collect-docs.test.ts` and `collect-docs.package-docs.test.ts`: 3 files, 88 passed. `pnpm --filter @objectstack/cli typecheck`: exit 0. The integration tier is left to CI: no spawn entry or integration-tier file is touched. - New spec pins in `src/ui/app-doc-nav-item.test.ts`: - the four accepted shapes, plus a doc entry nested in a group; - the missing-target refusal (`custom` issue at `['navigation', 0]`, message names both keys) on the union mount and on the standalone export; - a filename or path as `doc` is refused, and so is an empty `book`; - the alias answers; - the JSON Schema projection. - New CLI pins in `collect-docs.nav-target.test.ts`: unknown doc and unknown book are refused with a did-you-mean and the remedy. Also covered: the implicit book (package id), package-body books, group / area / contribution walks, and a real `src/docs/*.md` doc that resolves through `collectAndLintDocs`. - Existing enumerations updated: `app-strictness-batch19.test.ts` (nine exported branches), `app.test.ts` ("accepts every variant"), `alias-integrity.test.ts` (ten nav tables, two prose targets), `app.nav-type-assertions.ts` (doc probes, one `@ts-expect-error`). - Ablation, by the in-repo tool: `node scripts/ablation-replace.mjs --file …/app.zod.ts --anchor "}).refine(requiredOneOf(['book', 'doc']), {" --replacement "}).refine(() => true, {" -- vitest run src/ui/app-doc-nav-item.test.ts`. - Anchor count went 1 → 0 and the blob changed, so the mutation reached disk. **3 failed | 10 passed**: the two missing-target refusals and the projection pin. The rest stayed green. - Restored: blob == HEAD `7ac11bf63025` and `git diff HEAD` is empty. - No dist is involved: the spec suite resolves `./app.zod` from source. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 117 families at `f9baf5cb3f`. All 117 were run on that head and all exited 0. `--ran` reconciles "117 run, 0 NOT-MEASURED (a DERIVED zero — all 117 recorded an exit code and none of them is 3)". - First-pass reds fixed in this PR: `check:variant-docs` (ledger key + apps.mdx), `check:skill-identifier-liveness` (SKILL.md row), `check-skills-token-ratchet` (paid by deletion). - Prerequisite refusals (exit 3) rebuilt and re-run to 0: `check:skill-examples`, `check:i18n*`, `check:dual-build-cjs-loads`. - `npx eslint --no-inline-config` over the five touched TypeScript files: exit 0. This is a targeted run for my own check. The repo-wide `pnpm lint` is left to CI and is not claimed here. ## Acceptance notes - **No doc-in-book membership check.** When both `book` and `doc` are given, the build does not check that the doc is a member of the book. Membership is derived at read time from group rules (ADR-0046 §6.2.1), so a build-time check would be contradicted by the next doc a package adds. - **Book targets are resolved within the package.** A `book` target resolves to the stack's `books`, package-body `books`, or a package id. A book owned by a different package is refused. That matches the ruling's "in the package" and `docs/metadata-embed-ref`'s same-package posture. - **Doubled `allOf` in the generated JSON Schema.** The generator emits the projected `requiredOneOf` twice on `DocNavItem.json`, as it already does on `AssignmentExpressionValue.json`. The two entries are identical, so what the schema accepts is unchanged. Observation only. - **The console does not render `doc` items yet.** Until objectui#10188 ships in a pinned console, a `doc` item parses, is checked and publishes, but the menu does not show it. `apps.mdx` and the changeset both say so. ## 维护者速读(草稿) **改了什么**:应用菜单多了一种「文档」导航项(`type: 'doc'`),可以指向一本书(`book`)、一篇文档(`doc`),或者两个都填(书里的那一页);两个都不填会在保存时被拒,报错里点名两个键。构建(`os build` / `os validate` / `os lint`)时,如果填的书或文档在本包里不存在,会被拒并给出「你是不是想写 …」的提示。另外在对外技能包 `skills/objectstack-ui/SKILL.md` 的导航项表里加了一行。 **为什么改**:按 #19482 的裁决(A,含「可直接链接到 book」的追加)落 spec 这一半;技能包那一行是门禁 `check:skill-identifier-liveness` 要求的——新增的导航项种类必须在技能目录里有一行,否则 AI 作者读不到这个能力。 **风险与代价(含回滚)**:纯新增,不改变任何已有写法的判定;控制台要等 objectui#10188 才会画出这个菜单项,在此之前它能存、能校验、但菜单上看不到。技能包那一行的 token 用同文件删字抵账(顺手删掉了对象行里一句与 spec 不符的「目标优先级」说法)。回滚=整 PR revert,无数据迁移。 **席位意见**: **你要做的**:这个 PR 碰了 `skills/**`,是 Tier H,需要您亲自批准一次;另外请看一眼报告里的开放问题——服务端导航过滤要不要也按文档 audience 把看不了的「文档」项剪掉(目前只有渲染端剪、内容读取有门)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01VWsFyWDp8Rjb2Ma6a3Cyo8)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cdc1ae0 commit ccccdcc

26 files changed

Lines changed: 695 additions & 56 deletions

‎.changeset/19482-doc-nav-item.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/cli": minor
4+
---
5+
6+
**Clause-②: yes (widening)** — a new member (`type: 'doc'`) on the published, strict navigation-item union, and a new exported schema (`DocNavItemSchema`), so the accept set an app author writes against grows. Nothing previously admitted is refused: the `docs/nav-target` build rule judges only `doc` items, which no stack could carry before. Contract-review tier.
7+
8+
A documentation entry on the app menu: the new `type: 'doc'` navigation item (`DocNavItemSchema`, ADR-0046) targets a `book` and/or a `doc`, and at least one is required.
9+
10+
```ts
11+
{ id: 'nav_help', type: 'doc', label: 'Help Centre', book: 'crm_manual' } // opens the book
12+
{ id: 'nav_guide', type: 'doc', doc: 'crm_lead_guide' } // opens that page
13+
{ id: 'nav_both', type: 'doc', book: 'crm_manual', doc: 'crm_lead_guide' } // that page, in that book
14+
```
15+
16+
- **`book` alone** opens the book at its first readable page with the book sidebar. Membership is derived by the book's group rules, so a doc added later that matches a rule appears under the entry with no navigation edit. The package id also names a book — the package's implicit book.
17+
- **`doc` alone** opens that page; its book context is the doc's own book, else the package's implicit book. `doc` is a doc NAME (the source filename stem, lowercase snake_case): `crm_lead_guide.md` or `docs/crm_lead_guide` is refused.
18+
- **Neither** is refused when the app is parsed, with a message naming both keys. The rule also reaches the published JSON Schema (`json-schema/ui/DocNavItem.json`) as an `anyOf` of `required`, so a validator reading the schema refuses the same shape.
19+
- **Audience**: the entry has no gate of its own — it inherits the docs audience gate. A `book` entry shows the member only the pages they may read, and is not shown to a member who may read none; a `doc` entry the member may not read is not shown. `visible` / `requiredPermissions` can only narrow that further.
20+
- **`os build` / `os validate` / `os lint`** refuse a `doc` entry whose `book` or `doc` names nothing in the package (new rule `docs/nav-target`, with a did-you-mean). This runs in the docs step because that is where docs from `src/docs/*.md` join the artifact. It checks app `navigation`, `areas[].navigation` and `manifest.navigationContributions`.
21+
- Near-misses are answered: `docName` → `doc`, `bookName` → `book`, and `book` / `doc` written on another item type points at `type: 'doc'`.
22+
23+
The console renders the new entry in a later objectui release; until then a `doc` item parses and publishes, but the menu does not show it.

‎content/docs/references/api/metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const result = AppDefinitionResponseSchema.parse(data);
9292
| **isDefault** | `boolean` | optional (default: `false`) | Is default app |
9393
| **hidden** | `boolean` | optional | Hide from the App Switcher; the shell surfaces hidden apps via the avatar menu instead (navigation only — never an access gate) |
9494
| **_unpublished** | `boolean` | optional | Machine-managed publish gate (ADR-0045 §3) — true = unpublished, externally unobservable. Written by AI materialization, cleared by publish-drafts. Never authored. |
95-
| **navigation** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | optional | Full navigation tree for the app sidebar |
95+
| **navigation** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +8 more)[]` | optional | Full navigation tree for the app sidebar |
9696
| **areas** | `{ id: string; label: string \| Record<string, string>; icon?: string; description?: string \| Record<string, string>; … }[]` | optional | Navigation areas for partitioning navigation by business domain |
9797
| **contextSelectors** | `{ id: string; label: string \| Record<string, string>; icon?: string; optionsSource: object; … }[]` | optional | App-level scope dropdowns whose value is injected into nav items as `{<id>}` template vars |
9898
| **homePageId** | `never` | optional | [REMOVED] `app.homePageId` was removed in @objectstack/spec 17.0.0 (ADR-0049). objectui's console did read it before v17 (`resolveLandingRoute`), so this key had a consumer — it was retired because the capability is better expressed on the navigation item itself than as an ID cross-reference that silently falls back when it dangles. An app's landing page IS its first navigation item (by `order`), and the root landing follows `isDefault` routing. Delete the key; to change where an app opens, reorder `navigation` so the intended entry is first, and set `isDefault` on the app that should own the root landing. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |

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

Lines changed: 9 additions & 9 deletions
Large diffs are not rendered by default.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1934,7 +1934,7 @@ Install package request
19341934
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
19351935
| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
19361936
| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
1937-
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
1937+
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
19381938
| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (`manifest.runtime`) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the `node` tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation. |
19391939
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
19401940
| **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) |

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1537 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1538 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -32,8 +32,8 @@ counts are sums of the rows they head. Regenerate with
3232
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3434
| [System Protocol](/docs/references/system) | 34 | 275 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
35-
| [UI Protocol](/docs/references/ui) | 16 | 158 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **195** | **1537** | 14 protocol modules |
35+
| [UI Protocol](/docs/references/ui) | 16 | 159 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36+
| **Total** | **195** | **1538** | 14 protocol modules |
3737

3838
---
3939

@@ -361,15 +361,15 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
361361

362362
## UI Protocol
363363

364-
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 158 schemas**
364+
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 159 schemas**
365365

366366
Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer.
367367

368368
| File | Schemas |
369369
| :--- | :--- |
370370
| [`action.zod.ts`](/docs/references/ui/action) | `Action`, `ActionAi`, `ActionLocation`, `ActionParam`, `ActionType`, `InlineAction` |
371371
| [`action-params.zod.ts`](/docs/references/ui/action-params) | `ActionSession` |
372-
| [`app.zod.ts`](/docs/references/ui/app) | `ActionNavItem`, `App`, `AppBranding`, `AppContextSelector`, `ComponentNavItem`, `DashboardNavItem`, `GroupNavItem`, `NavigationArea`, `NavigationContribution`, `NavigationItem`, `ObjectNavItem`, `PageNavItem`, `ReportNavItem`, `UrlNavItem` |
372+
| [`app.zod.ts`](/docs/references/ui/app) | `ActionNavItem`, `App`, `AppBranding`, `AppContextSelector`, `ComponentNavItem`, `DashboardNavItem`, `DocNavItem`, `GroupNavItem`, `NavigationArea`, `NavigationContribution`, `NavigationItem`, `ObjectNavItem`, `PageNavItem`, `ReportNavItem`, `UrlNavItem` |
373373
| [`bulk-action.zod.ts`](/docs/references/ui/bulk-action) | `BulkActionDef`, `BulkActionExecution`, `BulkActionOperation`, `BulkActionParam` |
374374
| [`chart.zod.ts`](/docs/references/ui/chart) | `ChartAggregate`, `ChartAggregateFunction`, `ChartAnnotation`, `ChartAxis`, `ChartConfig`, `ChartDrillDown`, `ChartGroupBy`, `ChartInteraction`, `ChartSeries`, `ChartType` |
375375
| [`component.zod.ts`](/docs/references/ui/component) | `AIChatWindowProps`, `ElementButtonProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGanttProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMapProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `ObjectTimelineProps`, `ObjectTreeProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` |

‎content/docs/references/kernel/manifest.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const result = ManifestSchema.parse(data);
4444
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
4545
| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
4646
| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
47-
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
47+
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
4848
| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (`manifest.runtime`) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the `node` tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation. |
4949
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
5050
| **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) |
@@ -107,7 +107,7 @@ A navigation contribution: a package injecting nav items into an app it does not
107107
| **app** | `string` | ✅ | Target app name to contribute navigation into (e.g. "setup") |
108108
| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time. |
109109
| **priority** | `integer` | optional (default: `200`) | Merge priority within the target group — lower applied first (matches object extender priority) |
110-
| **items** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group |
110+
| **items** | `({ id: string; label?: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +8 more)[]` | ✅ | Navigation items contributed into the target app/group |
111111

112112
### Nested Shape: `Manifest.engine`
113113

0 commit comments

Comments
 (0)