diff --git a/.changeset/24450-nav-visible-data-needs-data-source.md b/.changeset/24450-nav-visible-data-needs-data-source.md new file mode 100644 index 00000000000..1a45b262f99 --- /dev/null +++ b/.changeset/24450-nav-visible-data-needs-data-source.md @@ -0,0 +1,31 @@ +--- +'@objectstack/spec': minor +--- + +A navigation entry whose `visible` reads `data` must declare `dataSource`: an entry with no `dataSource` whose `visible` predicate reads the top-level `data` root is refused at parse, at the entry's own `visible`, with the fix. + +Clause-②: no (narrowing) + + + +**BREAKING** (an accept-set narrowing on a published authoring surface: `NavigationItemSchema`), shipped as `minor` under the launch-window convention for breaking changes (Changesets is in pre mode on `main`, tag `next`). + +**Why.** A navigation entry may declare one API `dataSource`, whose selected value its `visible` reads as `data`. That is the only thing that puts a `data` root in a navigation predicate's scope: the console shell's ambient roots are `current_user`, `user`, `ctx.user`, `os.user` and `features`, and a group's `dataSource` is not published to its children. An entry that reads `data` without declaring a source has nothing to read, so until now it parsed, passed `os validate` and shipped as an entry the console never shows, with the author told nothing. + +**What is refused.** A navigation entry of any type (a `group` included) with no `dataSource` whose `visible`, a CEL predicate, reads the root `data`: `data.total == 1`, `!data.has_more`, `has(data.total)`, `.data.total == 1`, or `${data.total == 1}`. The answer is one `custom` issue at the entry's `visible` (for example `navigation.0.children.2.visible`), whose message begins *This navigation entry's `visible` reads `data`, but the entry declares no `dataSource`, so there is no `data` to read*, and names the fix. It is a check on `NavigationItemSchema` itself, so it reaches every door that parses a navigation entry: `app.navigation`, `app.areas[].navigation`, every `children` array at any depth, and a navigation contribution's `items`, through `defineApp`, `defineStack`, `os validate` / `os build` and the metadata write door. + +**What stays accepted, byte for byte.** An entry that declares `dataSource` and reads `data`; an entry whose `visible` reads only `current_user`, `features` or other ambient roots; a `data` that is a member name (`current_user.data.total`), text inside a string literal or a `//` comment, or a comprehension macro's own iteration variable (`current_user.positions.exists(data, data == 'org_admin')`); an entry with no `visible`; and a separator. + +**FROM → TO.** + +- FROM `{ id: 'nav_environments', type: 'object', objectName: 'sys_environment', visible: 'data.total == 1' }` +- TO `{ id: 'nav_environments', type: 'object', objectName: 'sys_environment', dataSource: { provider: 'api', read: { url: '/api/v1/cloud/home' }, path: 'data.environments' }, visible: 'data.total == 1' }`, or drop the `data.*` read and gate on `current_user` / `features`. + +One-line fix: declare `dataSource` on the entry whose `visible` reads `data`, or drop the `data.*` read. A child of a group that declares a `dataSource` declares its own. No conversion can do this for you: only the author knows which endpoint the entry was meant to read. + +**Who is affected, measured.** No navigation entry in this repository reads `data` without a `dataSource`: an AST census of every tracked `.ts` / `.tsx` / `.mts` file found 11 navigation entries with a `visible`, of which 2 read `data` (both spec tests, both declaring a `dataSource`) and 3 read `current_user`. Deployed metadata was not measured. + +### The kit + +- **The refusal.** A module-private CEL root reader and the refinement that uses it, mounted once on `NavigationItemSchema` in `ui/app.zod.ts`. The `visible` description now says the read is refused without a `dataSource`, and the generated reference page carries it. +- **The ledger.** The D3 semantic entry `nav-item-visible-data-needs-data-source` (protocol 18). No key is removed, so there is no tombstone, and there is no D2 conversion. diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 017bc144f37..5687afe2c1a 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -34,7 +34,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -116,7 +116,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -140,7 +140,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -158,7 +158,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -177,7 +177,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -196,7 +196,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -214,7 +214,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -232,7 +232,7 @@ const result = ActionNavItemSchema.parse(data); | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -253,7 +253,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -280,7 +280,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -372,7 +372,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -404,7 +404,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -437,7 +437,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -469,7 +469,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -510,7 +510,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -534,7 +534,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -552,7 +552,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -571,7 +571,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -590,7 +590,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -608,7 +608,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -626,7 +626,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -647,7 +647,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -674,7 +674,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -709,7 +709,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -733,7 +733,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -751,7 +751,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -770,7 +770,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -789,7 +789,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -807,7 +807,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -825,7 +825,7 @@ A navigation contribution: a package injecting nav items into an app it does not | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -846,7 +846,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -873,7 +873,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -905,7 +905,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -943,7 +943,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -975,7 +975,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1008,7 +1008,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1041,7 +1041,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1073,7 +1073,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1112,7 +1112,7 @@ This schema accepts one of the following structures: | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1147,7 +1147,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1194,7 +1194,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1228,7 +1228,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1264,7 +1264,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1296,7 +1296,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | @@ -1327,7 +1327,7 @@ Documentation entry on the app menu (ADR-0046). Targets a `book` and/or a `doc` | **order** | `number` | optional | Sort order within the same level (lower = first) | | **badge** | `string \| number` | optional | Badge text or count displayed on the item | | **badgeVariant** | `Enum<'default' \| 'secondary' \| 'destructive' \| 'outline'>` | optional | Visual variant of the nav badge. Declared to match the objectui NavigationRenderer read (inverse-drift fix, liveness audit /). | -| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read. | +| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL). e.g. P`'org_admin' in current_user.positions`. An entry that declares `dataSource` reads that binding's selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read. | | **dataSource** | `{ provider: 'api'; read: object; path?: string }` | optional | [PLANNED — not yet rendered] One API data source for this navigation entry, the page element's API binding by reference: `{ provider: 'api', read: { url }, path? }` reads one same-origin endpoint with the console's signed-in fetch and binds the value `path` selects, which this entry's `visible` reads as `data` (e.g. `data.total == 1`). Only `visible` reads it: `label` and the target keys (`recordId`, `filters`, `params`) take no `data` read, and the binding is not published to `children`. The same refusals as the page element's binding apply: `read.url` must be a same-origin API path, `method` is `GET` alone, and no credential key is written. No console renders it yet, so until the navigation renderer reads it, a `visible` that reads `data` has nothing to read. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this item | | **requiresObject** | `string` | optional | Hide/disable this entry unless the named object is registered in the runtime | diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json index cf82a851456..bbe91d21d55 100644 --- a/packages/spec/dropped-refinements.baseline.json +++ b/packages/spec/dropped-refinements.baseline.json @@ -3,7 +3,7 @@ "measured": { "zod": "4.4.3", "publishedSchemasWithDroppedRefinements": 240, - "droppedRefinementSites": 781, + "droppedRefinementSites": 812, "refinementSitesThatDidProject": 369, "refinementSitesWithNoJsonFormToCompare": 0 }, @@ -62,6 +62,7 @@ }, "api/AppDefinitionResponse": { "sites": [ + "data.navigation.element.lazy", "data.navigation.element.lazy.options[0]" ] }, @@ -93,6 +94,7 @@ "manifest.hooks.element.object", "manifest.jobs.element", "manifest.jobs.element.schedule.options[0].timezone", + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]", "manifest.objectExtensions.element", "manifest.objects.element", @@ -154,11 +156,13 @@ }, "api/DisablePackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/EnablePackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -184,6 +188,7 @@ }, "api/GetInstalledPackageResponse": { "sites": [ + "data.options[0].manifest.navigationContributions.element.items.element.lazy", "data.options[0].manifest.navigationContributions.element.items.element.lazy.options[0]", "data.options[1].manifest.actions.element.in", "data.options[1].manifest.actions.element.in.ai.outputSchema", @@ -249,6 +254,7 @@ }, "api/GetPackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -282,16 +288,19 @@ }, "api/InstallPackageRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/InstallPackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/InstalledPackageAtEitherStage": { "sites": [ + "options[0].manifest.navigationContributions.element.items.element.lazy", "options[0].manifest.navigationContributions.element.items.element.lazy.options[0]", "options[1].manifest.actions.element.in", "options[1].manifest.actions.element.in.ai.outputSchema", @@ -355,6 +364,7 @@ }, "api/ListInstalledPackagesResponse": { "sites": [ + "data.packages.element.options[0].manifest.navigationContributions.element.items.element.lazy", "data.packages.element.options[0].manifest.navigationContributions.element.items.element.lazy.options[0]", "data.packages.element.options[1].manifest.actions.element.in", "data.packages.element.options[1].manifest.actions.element.in.ai.outputSchema", @@ -413,6 +423,7 @@ }, "api/ListPackagesResponse": { "sites": [ + "packages.element.manifest.navigationContributions.element.items.element.lazy", "packages.element.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -449,31 +460,37 @@ }, "api/PackageInstallBody": { "sites": [ + "options[1].navigationContributions.element.items.element.lazy", "options[1].navigationContributions.element.items.element.lazy.options[0]" ] }, "api/PackageInstallManifest": { "sites": [ + "navigationContributions.element.items.element.lazy", "navigationContributions.element.items.element.lazy.options[0]" ] }, "api/PackageInstallRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/PackageInstallResponse": { "sites": [ + "data.package.manifest.navigationContributions.element.items.element.lazy", "data.package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/PackageUpgradeRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "api/ResolveDependenciesRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -1022,41 +1039,49 @@ }, "kernel/DisablePackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/EnablePackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/GetPackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/InstallPackageRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/InstallPackageResponse": { "sites": [ + "package.manifest.navigationContributions.element.items.element.lazy", "package.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/InstalledPackage": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/ListPackagesResponse": { "sites": [ + "packages.element.manifest.navigationContributions.element.items.element.lazy", "packages.element.manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/Manifest": { "sites": [ + "navigationContributions.element.items.element.lazy", "navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -1091,11 +1116,13 @@ }, "kernel/UpgradePackageRequest": { "sites": [ + "manifest.navigationContributions.element.items.element.lazy", "manifest.navigationContributions.element.items.element.lazy.options[0]" ] }, "kernel/UpgradeSnapshot": { "sites": [ + "previousManifest.navigationContributions.element.items.element.lazy", "previousManifest.navigationContributions.element.items.element.lazy.options[0]" ] }, @@ -1378,6 +1405,7 @@ }, "ui/App": { "sites": [ + "navigation.element.lazy", "navigation.element.lazy.options[0]" ] }, @@ -1527,16 +1555,19 @@ }, "ui/NavigationArea": { "sites": [ + "navigation.element.lazy", "navigation.element.lazy.options[0]" ] }, "ui/NavigationContribution": { "sites": [ + "items.element.lazy", "items.element.lazy.options[0]" ] }, "ui/NavigationItem": { "sites": [ + "lazy", "lazy.options[0]" ] }, diff --git a/packages/spec/src/migrations/entries/semantic/18.nav-item-visible-data-needs-data-source.ts b/packages/spec/src/migrations/entries/semantic/18.nav-item-visible-data-needs-data-source.ts new file mode 100644 index 00000000000..e40bede16dc --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.nav-item-visible-data-needs-data-source.ts @@ -0,0 +1,43 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +// The D3 entry for the navigation `visible` / `dataSource` narrowing (#24450, +// question 2 of the #24332 ruling, letter A). It narrows a navigation entry's +// accept set; no key is removed, so there is no tombstone and no +// RETIRED_KEYS_BY_MAJOR row. There is no D2 conversion either: which endpoint +// an entry was meant to read is the author's decision, and dropping the read +// would change what the gate means. +export const entry: SemanticMigration = { + id: 'nav-item-visible-data-needs-data-source', + // No backticks and no pipes in `surface` — build-upgrade-guide.ts renders it + // inside a code span. + surface: + 'A navigation entry whose visible predicate (CEL) reads the top-level data root while the same entry ' + + 'declares no dataSource. Judged at every navigation door: app navigation, app areas navigation, the ' + + 'children of any entry at any depth, and a navigation contribution items list.', + replacement: + 'Declare a `dataSource` on that entry (`{ provider: \'api\', read: { url }, path? }`, the page element\'s ' + + 'API binding), whose selected value the entry\'s `visible` reads as `data`; or drop the `data.*` read and ' + + 'gate the entry on the ambient roots (`current_user`, `features`) instead. A group\'s `dataSource` is not ' + + 'published to its children, so a child that reads `data` declares its own.', + reason: + 'Declared means enforced (ADR-0049, the enforce arm). In a navigation predicate, `data` is the value the ' + + 'entry\'s own `dataSource` binds, and nothing else puts a `data` root in its scope: the console shell\'s ' + + 'ambient roots are `current_user`, `user`, `ctx.user`, `os.user` and `features`. An entry that reads ' + + '`data` without declaring a source therefore has nothing to read; before this change it parsed, passed ' + + '`os validate` and shipped as an entry the console never shows, with the author told nothing at ' + + 'authoring time. Now refused at the entry\'s own `visible`, with the fix in the message. Ships at once, ' + + 'no grace window and no dual spelling. Not convertible: only the author knows which endpoint the entry ' + + 'was meant to read.', + acceptanceCriteria: + 'WHICH DOOR: the spec schema\'s refusal on `NavigationItemSchema`, so it lands wherever an app or a ' + + 'navigation contribution is parsed through `@objectstack/spec` (`defineApp`, `defineStack`, ' + + '`os validate` / `os build`, and the metadata write door), as one `custom` issue at the entry\'s ' + + '`visible`. The reader is a CEL lexer that refuses only a certain root read: `data` as a member name, ' + + 'inside a string or a comment, or bound by a comprehension macro is not one. Fix each such entry by ' + + 'declaring its `dataSource`, or by gating it on another root. An entry that declares a `dataSource`, and ' + + 'an entry whose `visible` reads only `current_user` or `features`, parses byte-identically to before. ' + + 'Census at the time of the change: no navigation entry in this repository reads `data` without a ' + + '`dataSource`; the two that read `data` are spec tests that declare one.', +}; diff --git a/packages/spec/src/ui/app-nav-visible-data-needs-data-source.test.ts b/packages/spec/src/ui/app-nav-visible-data-needs-data-source.test.ts new file mode 100644 index 00000000000..9f1225f1aaf --- /dev/null +++ b/packages/spec/src/ui/app-nav-visible-data-needs-data-source.test.ts @@ -0,0 +1,131 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#24450] A navigation entry whose `visible` reads `data` must declare + * `dataSource` — question 2 of #24332's ruling, letter A: the narrowing arm. + * + * `data` in a navigation predicate is the value the entry's OWN `dataSource` + * binds (#24332 shape 1). An entry that reads it without declaring one has + * nothing to read, so it is refused at parse, at the entry's `visible`, with + * the fix in the message. What is pinned: + * + * 1. the refusal: one `custom` issue at `visible` (re-rooted under every + * navigation door: `navigation`, a group's `children`, `areas[].navigation` + * and a contribution's `items`), whose message names the fix; + * 2. what stays accepted: the same entry with a `dataSource`, and entries + * whose predicates read only the ambient roots (`current_user`, `features`); + * 3. the reader's soundness edges: a `data` that is a member name, a string, + * a comment, or a macro's own iteration variable is not a root read. + */ +import { describe, it, expect } from 'vitest'; + +import { AppSchema, NavigationContributionSchema, NavigationItemSchema } from './app.zod'; + +const API_BINDING = { provider: 'api', read: { url: '/api/v1/cloud/home' }, path: 'data.environments' } as const; +const entry = (visible: unknown, extra: Record = {}) => + ({ id: 'nav_environments', type: 'object', objectName: 'sys_environment', visible, ...extra }); + +const issuesOf = (r: { success: boolean; error?: { issues: Array<{ code: string; path: PropertyKey[]; message: string }> } }) => + (r.error?.issues ?? []).map((i) => ({ code: i.code, path: i.path, message: i.message })); + +const expectRefusedAt = (r: Parameters[0], path: PropertyKey[]) => { + expect(r.success, 'a `data` read with no `dataSource` must be refused').toBe(false); + const issues = issuesOf(r); + expect(issues).toHaveLength(1); + expect(issues[0]).toMatchObject({ code: 'custom', path }); + expect(issues[0].message).toContain('declares no `dataSource`'); + expect(issues[0].message).toContain('Declare `dataSource` on this entry'); + expect(issues[0].message).toContain('drop the `data.*` read'); +}; + +describe('a `visible` that reads `data` on an entry with no `dataSource` is refused at parse', () => { + it.each([ + ['a comparison', 'data.total == 1'], + ['a negation', '!data.has_more'], + ['the bare root', 'data'], + ['a `has()` test', 'has(data.total)'], + ['beside an ambient root', '\'org_admin\' in current_user.positions && data.total > 1'], + ['the leading-dot absolute name', '.data.total == 1'], + ['a `${…}`-wrapped predicate', '${data.total == 1}'], + ['an index read', 'data[\'total\'] == 1'], + ['after a raw string ending in a backslash', 'r\'\\\' == data.sep'], + ])('refuses %s (`%s`) at `visible`', (_name, visible) => { + expectRefusedAt(NavigationItemSchema.safeParse(entry(visible)), ['visible']); + }); + + it('refuses the `{ dialect: \'cel\', source }` envelope the same way', () => { + expectRefusedAt( + NavigationItemSchema.safeParse(entry({ dialect: 'cel', source: 'data.total == 1' })), + ['visible'], + ); + }); + + it('refuses on every branch that spreads the base, a `group` included', () => { + for (const target of [ + { type: 'dashboard', dashboardName: 'ops' }, + { type: 'page', pageName: 'environment_home' }, + { type: 'url', url: '/apps/cloud' }, + { type: 'group', children: [] }, + ]) { + const r = NavigationItemSchema.safeParse({ id: 'nav_x', visible: 'data.total == 1', ...target }); + expectRefusedAt(r, ['visible']); + } + }); + + it('refuses at the entry\'s own path through every navigation door', () => { + expectRefusedAt( + AppSchema.safeParse({ name: 'cloud_console', label: 'Cloud', navigation: [entry('data.total == 1')] }), + ['navigation', 0, 'visible'], + ); + // A group's binding is NOT published to its children: the child reads nothing. + expectRefusedAt( + AppSchema.safeParse({ + name: 'cloud_console', label: 'Cloud', + navigation: [{ + id: 'grp_tenancy', type: 'group', label: 'Tenancy', dataSource: API_BINDING, visible: 'data.total > 0', + children: [{ id: 'nav_home', type: 'page', pageName: 'environment_home', visible: 'data.total == 1' }], + }], + }), + ['navigation', 0, 'children', 0, 'visible'], + ); + expectRefusedAt( + AppSchema.safeParse({ + name: 'cloud_console', label: 'Cloud', + areas: [{ id: 'area_main', label: 'Main', navigation: [entry('data.total == 1')] }], + }), + ['areas', 0, 'navigation', 0, 'visible'], + ); + expectRefusedAt( + NavigationContributionSchema.safeParse({ app: 'setup', items: [entry('data.total == 1')] }), + ['items', 0, 'visible'], + ); + }); +}); + +describe('what stays accepted', () => { + it('the same entry with a `dataSource` parses, and keeps its predicate', () => { + const r = NavigationItemSchema.safeParse(entry('data.total == 1', { dataSource: API_BINDING })); + expect(r.success, JSON.stringify(issuesOf(r))).toBe(true); + expect((r.data as { visible?: unknown }).visible).toEqual({ dialect: 'cel', source: 'data.total == 1' }); + }); + + it.each([ + ['a `current_user` read', '\'org_admin\' in current_user.positions'], + ['a `features` read', 'features.cloud_billing == true'], + ['both ambient roots', 'current_user.id != \'\' && features.beta'], + ['a member named `data`', 'current_user.data.total == 1'], + ['`data` inside a string literal', '\'data.total\' in current_user.positions'], + ['`data` inside a triple-quoted string', '"""data.total""" == current_user.id'], + ['`data` inside a comment', 'current_user.id != \'\' // data.total == 1'], + ['a macro iteration variable named `data`', 'current_user.positions.exists(data, data == \'org_admin\')'], + ['a field named `data` after a selection on a call', 'size(current_user.positions).data == 1'], + ])('%s parses with no `dataSource` (`%s`)', (_name, visible) => { + const r = NavigationItemSchema.safeParse(entry(visible)); + expect(r.success, JSON.stringify(issuesOf(r))).toBe(true); + }); + + it('an entry with no `visible`, and a separator, are untouched', () => { + expect(NavigationItemSchema.safeParse({ id: 'nav_plain', type: 'object', objectName: 'sys_environment' }).success).toBe(true); + expect(NavigationItemSchema.safeParse({ type: 'separator', id: 'sep_1' }).success).toBe(true); + }); +}); diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 791b294363f..90cf1bbc6c5 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -364,9 +364,11 @@ const BaseNavItemSchema = z.object({ * element's own reading of its API binding in its `visibleWhen`, one dialect * (CEL, the bare string normalised to `{ dialect: 'cel', source }`) and one * root name. PLANNED, not yet rendered: see that key. An entry with no - * `dataSource` has no `data` to read. + * `dataSource` has no `data` to read, and a `visible` that reads `data` on + * such an entry is REFUSED at parse, at this key, with the fix + * ({@link navItemVisibleDataNeedsDataSource}). */ - visible: EvaluatedExpressionInputSchema.optional().describe('Visibility predicate (CEL). e.g. P`\'org_admin\' in current_user.positions`. An entry that declares `dataSource` reads that binding\'s selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read.'), + visible: EvaluatedExpressionInputSchema.optional().describe('Visibility predicate (CEL). e.g. P`\'org_admin\' in current_user.positions`. An entry that declares `dataSource` reads that binding\'s selected value here as `data`, e.g. `data.total == 1` (planned: no console renders `dataSource` yet); an entry with no `dataSource` has no `data` to read, and a `visible` that reads `data` there is refused at parse: declare `dataSource` on the entry, or drop the `data.*` read.'), /** * One API data source for this entry — the page element's API binding, @@ -891,6 +893,169 @@ export type NavigationItemInput = | z.input | (z.input & { children: NavigationItemInput[] }); +/** + * The CEL comprehension macros (and `cel.bind`) whose FIRST argument binds a + * variable rather than reading one: in `xs.exists(data, data > 1)` the name + * `data` is the macro's own iteration variable. A predicate that binds `data` + * this way is not judged at all by {@link celReadsRootData} — the reader + * would have to track the binding's scope to tell a bound `data` from the + * root one, and an unsure reader must never refuse. + */ +const CEL_BINDING_MACROS: ReadonlySet = new Set([ + 'all', 'exists', 'exists_one', 'existsOne', 'map', 'filter', 'bind', + 'transformList', 'transformMap', 'transformMapEntry', 'sortBy', +]); + +type CelToken = { kind: 'ident' | 'operand' | 'punct'; text: string }; + +/** + * Lex a CEL source into the three token kinds the root reader needs: an + * identifier, an operand that is not an identifier (a string, bytes or number + * literal, a backquoted field name), and one punctuation character. String + * literals — single, double and triple quoted, with the `r` / `b` prefixes — + * and `//` comments are consumed whole, so a `data` inside a quoted string or + * a comment is never read as an identifier. + */ +function lexCel(source: string): CelToken[] { + const tokens: CelToken[] = []; + let i = 0; + const skipString = (start: number, raw: boolean): number => { + const quote = source[start]; + const triple = source.startsWith(quote.repeat(3), start); + const close = triple ? quote.repeat(3) : quote; + let j = start + close.length; + while (j < source.length) { + if (!raw && source[j] === '\\') { j += 2; continue; } + if (source.startsWith(close, j)) return j + close.length; + if (!triple && source[j] === '\n') return j; + j += 1; + } + return j; + }; + while (i < source.length) { + const ch = source[i]; + if (/\s/.test(ch)) { i += 1; continue; } + if (source.startsWith('//', i)) { + const eol = source.indexOf('\n', i); + i = eol === -1 ? source.length : eol; + continue; + } + if (ch === '"' || ch === '\'') { + i = skipString(i, false); + tokens.push({ kind: 'operand', text: 'string' }); + continue; + } + if (ch === '`') { + const end = source.indexOf('`', i + 1); + i = end === -1 ? source.length : end + 1; + tokens.push({ kind: 'operand', text: 'field' }); + continue; + } + if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(source[i + 1] ?? ''))) { + const m = /^(0[xX][0-9a-fA-F]+|[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?)[uU]?/.exec(source.slice(i)); + i += m ? m[0].length : 1; + tokens.push({ kind: 'operand', text: 'number' }); + continue; + } + if (/[A-Za-z_]/.test(ch)) { + const word = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i))![0]; + const next = source[i + word.length]; + if (/^(r|b|rb|br)$/i.test(word) && (next === '"' || next === '\'')) { + i = skipString(i + word.length, /r/i.test(word)); + tokens.push({ kind: 'operand', text: 'string' }); + continue; + } + tokens.push({ kind: 'ident', text: word }); + i += word.length; + continue; + } + tokens.push({ kind: 'punct', text: ch }); + i += 1; + } + return tokens; +} + +/** + * Does this CEL source read the top-level identifier `data`? + * + * A reader of ONE fact, sound in the direction a refusal needs: it answers + * `true` only for an occurrence that can be nothing but a read of the root + * `data` — not a member selection (`x.data`), not a call named `data`, not a + * field or key name written before a `:` in a `{ … }` literal, not text inside + * a string or a comment — and it answers `false` for the whole predicate when + * a comprehension macro binds a variable named `data` + * ({@link CEL_BINDING_MACROS}). A leading-dot absolute name (`.data.total`) + * is a root read and answers `true`. It is a lexer, not a CEL parser: a + * syntactically broken predicate is not refused here (the evaluator owns that + * verdict), it is only asked whether it names the root. + * + * Not a runtime dependency on `@objectstack/formula` (Prime Directive #2: + * `packages/spec` imports no runtime package); the settings manifest's own + * `visible` grammar check in `system/settings-manifest.zod.ts` is the + * precedent for a predicate reader living beside the schema that refuses on it. + */ +function celReadsRootData(source: string): boolean { + const tokens = lexCel(source); + const isOperandEnd = (t: CelToken | undefined): boolean => + !!t && (t.kind !== 'punct' || t.text === ')' || t.text === ']' || t.text === '}'); + let reads = false; + for (let k = 0; k < tokens.length; k += 1) { + const t = tokens[k]; + if (t.kind !== 'ident' || t.text !== 'data') continue; + const prev = tokens[k - 1]; + const next = tokens[k + 1]; + // A macro binding `data` as its iteration variable: `m(data, …)`. + if (prev?.text === '(' && next?.text === ',' && tokens[k - 2]?.kind === 'ident' + && CEL_BINDING_MACROS.has(tokens[k - 2].text)) { + return false; + } + if (prev?.text === '.' && isOperandEnd(tokens[k - 2])) continue; // `x.data` — a member selection + if (next?.text === '(') continue; // `data(…)` — a call, not a variable + if (next?.text === ':' && (prev?.text === '{' || prev?.text === ',')) continue; // a field / key name + reads = true; + } + return reads; +} + +/** + * A navigation entry whose `visible` reads `data` must declare `dataSource` — + * the narrowing arm of #24332's ruling, question 2 (letter A). + * + * `data` is the value an entry's OWN `dataSource` binds; nothing else puts a + * `data` root in a navigation predicate's scope (the shell's ambient roots are + * `current_user`, `user`, `ctx.user`, `os.user` and `features`), and a group's + * binding is not published to its children. So an entry that reads `data` + * without declaring a source has nothing to read, and before this check it + * parsed, validated and shipped as an entry the console never shows, with the + * author told nothing. Refused at the entry's own `visible`, with the fix. + * + * Judged on the PARSED entry, where `visible` is always the `{ dialect, source }` + * envelope; only the `cel` dialect is read. Mounted once, on + * {@link NavigationItemSchema} itself, so it reaches every navigation door — + * `app.navigation`, `app.areas[].navigation`, every `children` array at any + * depth, and a navigation contribution's `items`. The exported per-branch + * schemas do not chain it, the same posture as + * {@link objectNavTargetExclusivity}. Its migration record is the ADR-0087 D3 + * semantic entry `nav-item-visible-data-needs-data-source` (protocol 18). + */ +function navItemVisibleDataNeedsDataSource(item: unknown, ctx: z.RefinementCtx): void { + if (!item || typeof item !== 'object') return; + const entry = item as { visible?: unknown; dataSource?: unknown }; + if (entry.dataSource !== undefined) return; + const visible = entry.visible as { dialect?: unknown; source?: unknown } | undefined; + if (!visible || visible.dialect !== 'cel' || typeof visible.source !== 'string') return; + if (!celReadsRootData(visible.source)) return; + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['visible'], + message: + 'This navigation entry\'s `visible` reads `data`, but the entry declares no `dataSource`, so there is no ' + + '`data` to read: `data` is the value an entry\'s own `dataSource` binds, and a group\'s binding is not ' + + 'published to its children. Declare `dataSource` on this entry (`{ provider: \'api\', read: { url }, ' + + 'path? }`), or drop the `data.*` read and gate on `current_user` or `features` instead.', + }); +} + /** * Recursive Union of all navigation item types. * Allows constructing an unlimited-depth navigation tree. @@ -938,7 +1103,8 @@ export const NavigationItemSchema: z.ZodType, ...z.ZodObject[]]) as unknown as z.ZodType + ] as unknown as readonly [z.ZodObject, ...z.ZodObject[]]) + .superRefine(navItemVisibleDataNeedsDataSource) as unknown as z.ZodType ); /**