Repository navigation
Commit 256c86b
fix(lint)!: refuse an app nav
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XT5d6rCa6Ukdbma6oi1ufki)_
Fixes #28663
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no file under `skills/**` teaches a navigation
entry's `visible` grammar or the page `variables` list; the refusals
carry their own fix and `os explain` entries.
## 维护者速读
- **改了什么**:应用导航项的 `visible` 写成解析不了的 CEL(如 `'org_owner' in`),现在和页面组件的
`visibleWhen` 一样被拒:保存、草稿诊断、发布、`os validate` 四处同一条结论。页面 `variables`
新增一条规则:重名、空名、默认值类型和声明类型不符(`number` 配 `"abc"`)都拒。
- **为什么改**:以前这两类都能保存、草稿显示
valid、照常发布;导航项到了控制台解析失败,对所有人隐藏,作者毫不知情。变量重名时控制台只留最后一个,默认值类型错则所有读它的谓词比的都是错类型的值。
- **风险与回滚**:低到中。只收紧写入口,已存的应用和页面照常加载、照常服务,只有下次保存才被判。仓内语料(examples 和平台包)里
1 个导航 `visible`、5 个页面变量,零命中。回滚即撤回 `@objectstack/lint` 的这次改动。
- **先看**:`packages/lint/src/validate-visibility-predicates.ts`(新增的导航遍历和
`syntaxFinding`);`packages/lint/src/validate-page-variables.ts`(新规则);`packages/lint/src/authoring-rules.ts`(家族加
`app`、新规则注册)。
- **两分钟验证**:`pnpm --filter @objectstack/lint exec vitest run
src/runtime-gate.app-nav-visible-predicate-writes.test.ts
src/runtime-gate.page-variable-writes.test.ts`,预期 31/31 通过。
- **需要你做的**:无,席位落地。
## What changes
- `packages/lint/src/validate-visibility-predicates.ts`:
`validateVisibilityPredicates` now also walks `apps[].navigation`,
`apps[].areas[].navigation` and every entry's `children`, and runs the
existing `visibility-predicate-syntax` judgment (`celRefusal`, formula's
`parseCelToAstWithReason`, no second parser) on each entry's `visible`.
A bare string and a `{ dialect: 'cel' }` envelope are judged (the
console evaluates both on the CEL engine since #27457); an envelope of
another dialect is the console's legacy reading and a boolean is no
predicate, so neither is judged. The finding is located at the key
(`apps[0].navigation[0].visible`). The syntax finding is built by one
helper, `syntaxFinding`, for page/view elements and nav entries alike;
only two words move per surface: the consequence clause (a nav gate
falls closed, so "the entry is hidden from every viewer") and the remedy
example (`'org_admin' in current_user.positions`, the spec's own nav
example, since a nav entry binds no `record`). Page and view wording is
byte-identical (live probe below shows the same page message on base and
head).
- Only the syntax verdict runs on a nav entry, by measurement: the
family's reference verdicts judge roots a view or page binds (`record`,
`current_user`, `page`), and a nav entry binds `current_user`, `user`,
`ctx.user`, `os.user`, `features` (plus `data` beside a `dataSource`),
so `features.cloudManaged != true` would be refused as an unbound root.
- `packages/lint/src/validate-page-variables.ts` (new) with three ids:
`page-variable-name-duplicate` (at the second entry's `name`, naming the
first index), `page-variable-name-empty` (empty or blank),
`page-variable-default-type` (a `defaultValue` of another kind than
`type`: string for `string`/`record_id`, finite number, boolean, plain
object, array; `null` admitted for any type; no `type` = the schema's
default `string`). No `packages/spec/**` edit: the rule needs no shape
change.
- `packages/lint/src/authoring-rules.ts`: the visibility family
(`validateVisibilityPredicates`, `validatePredicatePathRefs`) declares
`runtimeTypes: ['view', 'page', 'app']` (the family stays on one type
set, as `authoring-rule-wiring.test.ts` pins; the path rule judges
nothing on an app). `validatePageVariables` is registered gating,
`CLI_AND_RUNTIME`, `runtimeTypes: ['page']`, after
`validatePrintPageBlocks`. No gate change in `metadata-protocol`: `app`
already maps to `apps`, and save, draft `_diagnostics` and publish all
read `judgeRuntimeAuthoringRules`.
- `packages/lint/src/index.ts`: exports the rule and its three id
constants. `packages/lint/src/rule-explanations.ts`: three `os explain`
entries, and a paragraph on `visibility-predicate-syntax` for nav
entries.
- `content/docs/**` (4 CLI transcripts): `author-time rules (57)` →
`(58)`, the count the registry derives with the new rule
(`check:docs-transcript-drift`).
- `.changeset/28663-nav-visible-syntax-page-variables.md`:
`@objectstack/lint` minor, BREAKING banner, both declaration lines,
ADR-0087 `not-required (no-migration-prescription)`.
**Migration note.** Nav `visible` values in the shipped corpus: 1
(`examples/app-crm` `current_user.can('crm_opportunity', 'read')`), 0
refused. Page variables: 5 on 2 showcase pages, 0 refused. `os validate`
on `example-crm`, `example-showcase`, `example-todo` at head: exit 0, 0
findings of the new ids. Stored apps and pages keep loading and serving;
only the next save of one with a finding is refused.
## Tests
-
`packages/lint/src/runtime-gate.app-nav-visible-predicate-writes.test.ts`
(new): the card's `visible: "'org_owner' in"` top-level, inside an area
and inside a group's `children` is refused at the app door at the key,
and `os validate` (`runAuthoringRules('validate')`) gives the same
finding; the `{ dialect: 'cel' }` envelope too. Controls admitted at
both: `'org_owner' in current_user.positions` (string and envelope),
`features.cloudManaged != true`, `current_user.can(...)`,
`user.isPlatformAdmin == true`, a `template` envelope, `true`.
- `packages/lint/src/runtime-gate.page-variable-writes.test.ts` (new):
duplicate, empty, blank, `number`+`"abc"`,
`boolean`/`array`/`object`/`record_id` mismatches and an untyped number
default, each refused at the page door with its path, with door/`os
validate` parity; four declaration sets admitted (the showcase's two
included, `null` defaults, `[]`); three faults on one page are three
findings.
-
`packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts`:
five new rows in the #26707 table (nav top-level, nav in an area, the
three variable faults): the draft saves, reads `valid: false` with the
publish's own words, and publish refuses `422 INVALID_METADATA`; clean
nav + variables read `valid: true` and publish; paths are item-relative
(`areas.0.navigation.0.visible`, `variables.0.defaultValue`).
-
`packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`:
the active `saveMetaItem` refuses the app (door finding = build finding
key by key, nothing stored) and each variable fault; clean controls
land.
- Pins updated for the app door's new rule list:
`validate-security-posture.runtime-surface.test.ts` (two),
`validate-predicate-path-refs.test.ts` (one), and the page door's
ordered list in `runtime-gate.page-visibility-predicate-writes.test.ts`.
<details><summary>Red on base 2678600, green on head</summary>
```
base: Test Files 2 failed (2) · Tests 20 failed | 11 passed (31) (the 11 = the controls)
head: Test Files 2 passed (2) · Tests 31 passed (31)
```
</details>
<details><summary>Ablations (scripts/ablation-replace.mjs, lint rebuilt,
ablation-dist-preflight both legs, restore = blob equals HEAD)</summary>
```
navA (nav syntax arm off) lint 7 failed | 24 passed · protocol 2 failed
dupB (duplicate check off) lint 3 failed | 13 passed · protocol 2 failed
emptyC (empty-name check off) lint 4 failed | 12 passed · protocol 2 failed
typeD (default-type check off) lint 8 failed | 8 passed · protocol 3 failed
every leg: mutate x0 -> x1, preflight exit 0, restore ok, git diff HEAD empty, rebuild, preflight --absent exit 0
```
Record: `pm-orders/server/evidence/28663/ablations.txt`.
</details>
<details><summary>Live probe: card steps 2 and 4 on a stock showcase
boot, base vs head</summary>
Commands: `pnpm --filter @objectstack/example-showcase dev -- --fresh -p
PORT`; sign in `POST /api/v1/auth/sign-in/email` as the seeded admin;
`POST /api/v1/packages` a scratch writable package; then `PUT
/api/v1/meta/app/qa_nav_app?mode=draft&package=PKG` (nav `visible:
"'org_owner' in"`), `GET …?state=draft&package=PKG`, `POST
/api/v1/packages/PKG/publish-drafts`; the same for a page with
`visibleWhen: "record.status =="` and a page with `variables` [number
"abc", "", duplicate]; and an active `PUT
/api/v1/meta/app/qa_nav_app2?package=PKG`. Base = origin/main lint
sources over the four edited files, lint rebuilt.
```
base step 2 PUT draft 200 · _diagnostics {"valid":true} · publish-drafts "outcome":"published"
base active PUT app 200 (state=active)
base step 4 page visibleWhen: _diagnostics valid:false visibility-predicate-syntax · publish refused INVALID_METADATA
base variables: _diagnostics {"valid":true}
head step 2 PUT draft 200 · _diagnostics valid:false [navigation.0.visible visibility-predicate-syntax "…so it never evaluates and the entry is hidden from every viewer"] · publish-drafts "outcome":"refused" INVALID_METADATA apps[0].navigation[0].visible
head active PUT app 422 INVALID_METADATA
head step 4 page visibleWhen: same message as base ("…the element always renders")
head variables: _diagnostics valid:false [variables.0.defaultValue page-variable-default-type, variables.1.name page-variable-name-empty, variables.2.name page-variable-name-duplicate]
os validate (app-crm, nav visible planted "'org_owner' in"): base exit 0 "Validation passed" · head exit 1 "rule: visibility-predicate-syntax at apps[0].navigation[1].children[0].visible"
```
Record: `pm-orders/server/evidence/28663/live-probe.txt` (with
`probe.sh`).
</details>
## Acceptance notes
Out of scope by the order, each measured at head on the same boot and
still admitted (`_diagnostics {"valid":true}`), not fixed here:
- A page variable `source` naming a component the page does not hold
(`source: "no_such_component"`): admitted.
- A `visibleWhen` reading an undeclared `page.nosuch`: admitted.
- Seen beside the work: a nav `visible` that is flawless CEL but over
the parse budget (a 90-clause `||` chain) is admitted at the draft read;
the page-side twin is `visibility-predicate-over-budget`. Not wired here
because the ruling names the syntax judgment only.
Also noted: when a nav `visible` uses an operator from another language
(`===`), the remedy's example comes from the shared operator table and
is `record`-rooted (`record.country == 'USA'`). The operator correction
itself is right on any surface.
Declaration: the claim's line reads `Clause-②: no`; this body and the
changeset carry `Clause-②: no (narrowing)`, because
`check-changeset-no-major.mjs` refuses `Metadata-protocol: narrowed`
beside a line without the `(narrowing)` arm. The order's Declarations
section names it a narrowing.
## Verification
Derived gate loop (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, 89 families with the docs edit) at
`bb33ace7`: 89 exit 0. `dispatch-gates --ran`: `89 derived famil(ies)
accounted for — 89 run, 0 NOT-MEASURED`. Record:
`pm-orders/server/28663-gates-ran.txt`.
- An earlier loop at `9fcdbbc4` read `check:docs-transcript-drift` exit
1 (the four transcripts printed 57 rules; the registry derives 58 with
the new rule), fixed in `bb33ace7`; and `check-plugin-teardown-shape
--self-test` exit 3 (shallow clone), exit 0 after `git fetch --depth=1
origin 621a487`.
- `check:type-check-debt` under the lock: `check-type-check-coverage
--re-measure: OK — 0 raw tsc error(s)`.
- Package suites at `9fcdbbc4` (merge of origin/main; `bb33ace7` adds
only the docs transcripts): `@objectstack/lint` Test Files 186 passed,
Tests 7749 passed; `@objectstack/metadata-protocol` Test Files 291
passed / 4 skipped, Tests 21963 passed / 26 skipped; both `typecheck`
exit 0. `eslint packages/lint packages/metadata-protocol
--no-inline-config` exit 0.
- Dogfood battery (the `(narrowing)` arm) at `bb33ace7`, closure built,
`pnpm --filter @objectstack/dogfood test` under the lock: VERDICT
command-exit 0 · Test Files 369 passed / 3 skipped (372) · Tests 2981
passed / 28 skipped (3009).
- CI owns the repo-wide lint and every family outside the derived set.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5nHMwmgiBBzV1TsunS
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5nHMwmgiBBzV1TsunS)_
---------
Co-authored-by: Claude <noreply@anthropic.com>visible that does not parse, and page variables that are duplicated, unnamed or wrong-typed (#28723)1 parent fa494d0 commit 256c86b
17 files changed
Lines changed: 978 additions & 28 deletions
File tree
- .changeset
- content/docs
- deployment
- getting-started
- ui
- packages
- lint/src
- metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
771 | 771 | | |
772 | 772 | | |
773 | 773 | | |
774 | | - | |
| 774 | + | |
775 | 775 | | |
776 | 776 | | |
777 | 777 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
759 | 759 | | |
760 | 760 | | |
761 | 761 | | |
762 | | - | |
| 762 | + | |
763 | 763 | | |
764 | 764 | | |
765 | 765 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
305 | 305 | | |
306 | 306 | | |
307 | 307 | | |
308 | | - | |
| 308 | + | |
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
404 | 404 | | |
405 | 405 | | |
406 | 406 | | |
407 | | - | |
| 407 | + | |
408 | 408 | | |
409 | 409 | | |
410 | 410 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
116 | 117 | | |
117 | 118 | | |
118 | 119 | | |
| |||
1355 | 1356 | | |
1356 | 1357 | | |
1357 | 1358 | | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
1358 | 1379 | | |
1359 | 1380 | | |
1360 | 1381 | | |
| |||
1782 | 1803 | | |
1783 | 1804 | | |
1784 | 1805 | | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
| 1813 | + | |
| 1814 | + | |
| 1815 | + | |
| 1816 | + | |
| 1817 | + | |
| 1818 | + | |
| 1819 | + | |
| 1820 | + | |
| 1821 | + | |
| 1822 | + | |
1785 | 1823 | | |
1786 | 1824 | | |
1787 | 1825 | | |
1788 | 1826 | | |
1789 | 1827 | | |
1790 | 1828 | | |
1791 | 1829 | | |
1792 | | - | |
| 1830 | + | |
1793 | 1831 | | |
1794 | 1832 | | |
1795 | 1833 | | |
| |||
1828 | 1866 | | |
1829 | 1867 | | |
1830 | 1868 | | |
1831 | | - | |
| 1869 | + | |
| 1870 | + | |
1832 | 1871 | | |
1833 | | - | |
| 1872 | + | |
1834 | 1873 | | |
1835 | 1874 | | |
1836 | 1875 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
674 | 674 | | |
675 | 675 | | |
676 | 676 | | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
677 | 687 | | |
678 | 688 | | |
679 | 689 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
962 | 962 | | |
963 | 963 | | |
964 | 964 | | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
965 | 970 | | |
966 | 971 | | |
967 | 972 | | |
| |||
2713 | 2718 | | |
2714 | 2719 | | |
2715 | 2720 | | |
| 2721 | + | |
| 2722 | + | |
| 2723 | + | |
| 2724 | + | |
| 2725 | + | |
| 2726 | + | |
| 2727 | + | |
| 2728 | + | |
| 2729 | + | |
| 2730 | + | |
| 2731 | + | |
| 2732 | + | |
| 2733 | + | |
| 2734 | + | |
| 2735 | + | |
| 2736 | + | |
| 2737 | + | |
| 2738 | + | |
| 2739 | + | |
| 2740 | + | |
| 2741 | + | |
| 2742 | + | |
| 2743 | + | |
| 2744 | + | |
| 2745 | + | |
| 2746 | + | |
| 2747 | + | |
| 2748 | + | |
| 2749 | + | |
| 2750 | + | |
| 2751 | + | |
| 2752 | + | |
| 2753 | + | |
| 2754 | + | |
| 2755 | + | |
| 2756 | + | |
| 2757 | + | |
| 2758 | + | |
| 2759 | + | |
| 2760 | + | |
| 2761 | + | |
| 2762 | + | |
| 2763 | + | |
| 2764 | + | |
| 2765 | + | |
| 2766 | + | |
| 2767 | + | |
2716 | 2768 | | |
2717 | 2769 | | |
2718 | 2770 | | |
| |||
5384 | 5436 | | |
5385 | 5437 | | |
5386 | 5438 | | |
| 5439 | + | |
| 5440 | + | |
| 5441 | + | |
5387 | 5442 | | |
5388 | 5443 | | |
5389 | 5444 | | |
| |||
0 commit comments