Skip to content

Commit 260ca2e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24289-hook-ctx-title-docs
2 parents 953159a + 959ee31 commit 260ca2e

19 files changed

Lines changed: 1307 additions & 108 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
fix(lint)!: a formula that reads, through a reference, a field the reference's target does not declare is refused where it is written
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) No spec key, stored shape or authorable spelling is added, removed or renamed. The field-formula pass now refuses one expression shape it accepted before: a one-hop read of a field the reference's target does not declare, which the runtime never computed a value for (the formula read null on every row). Nothing stored changes, so there is nothing for `objectstack migrate meta` to rewrite. -->
10+
11+
**BREAKING** (an authoring narrowing), shipped as `minor` under the launch-window convention for breaking changes.
12+
13+
The field-formula pass judges the field a formula reads one hop through a reference against the target object's fields: the fields it declares, the system columns the platform adds to it, and `id`. `os validate`, `os build` and the object save door (`PUT /api/v1/meta/object/:name`) run the same pass, under the existing `EXPRESSION_INVALID` rule.
14+
15+
- FROM: `record.book.name`, where `book` is a lookup to an object that declares no `name`, saved with a 200 and read `null` on every row. TO: refused (`422 INVALID_METADATA` at the save door). The message names the field and the target: "unknown field `name` on `<target>`".
16+
- `record.book.title`, where the target declares `title`, passes as before.
17+
- A target the stack does not carry is not judged. At the save door the stack carries the live registry's objects in the written package's closure, so a target from a package it depends on is judged.
18+
19+
What to do. Read a field the target declares, or declare the field on the target object. Do not declare it on the object that carries the formula. At `os build` and `os validate` this pass does not see a target field that an `objectExtensions` entry contributes, while the save door judges the merged object.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql): a formula reading a field its reference's target does not declare no longer makes the other formulas reading through the same reference read null
6+
7+
Clause-②: no
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author writes, and nothing stored, is added, removed or renamed. A valid formula that read null beside a misspelt sibling now reads its value; no spec key, export, option, object definition or stored shape changes, so there is nothing for `objectstack migrate meta` to rewrite. -->
10+
11+
The formula read loads, once per page, every column the object's formulas read through each reference. When one formula named a field the target does not declare, the whole reference was answered as undeclared. Every formula reading through it then read `null` and reported the other formula's field.
12+
13+
- The undeclared fields are left out of that shared read and judged for each formula on its own.
14+
- A formula that names one reads `null`, with a fault report naming only its own undeclared fields.
15+
- A formula that reads only declared fields through the same reference gets the value it gets when read alone (`?fields=`).
16+
17+
What to do. Nothing. A formula that read `null` this way now reads its value. To find the formula at fault, re-save the object: the save door refuses the misspelt read and names the field.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
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.
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: registered nav-item-visible-data-needs-data-source -->
10+
11+
**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`).
12+
13+
**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.
14+
15+
**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.
16+
17+
**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.
18+
19+
**FROM → TO.**
20+
21+
- FROM `{ id: 'nav_environments', type: 'object', objectName: 'sys_environment', visible: 'data.total == 1' }`
22+
- 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`.
23+
24+
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.
25+
26+
**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.
27+
28+
### The kit
29+
30+
- **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.
31+
- **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.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os doctor` tells a tool missing from PATH apart from a tool on PATH that failed when it ran
6+
7+
Clause-②: no
8+
9+
- **What was wrong.** `os doctor` runs `pnpm -v`, `tsc -v` and `git --version`, and reported every failure as "Not found". A `pnpm` that is installed but cannot start was reported `✗ pnpm Not found` with `Install pnpm: npm install -g pnpm@10.28.1`. One example is a Corepack-managed `pnpm` that cannot download the version it resolves: offline, behind a proxy, or with the registry answering 429. Reinstalling pnpm does not fix that.
10+
- **What changed.** When a probe fails, doctor checks whether the tool is on the PATH the probe ran with. This is the same lookup the shell does: `/bin/sh` walks `PATH`; on Windows, `cmd.exe` tries each `PATHEXT` extension in the current directory and then in each `PATH` entry. If the tool is not there, the row is unchanged. If it is there, the row's message is the tool's own first error line, such as Corepack's `Error: Server answered with HTTP 429 when performing the request to …`. The fix then matches the cause. For a Corepack download failure, it says to check the network or proxy, or to pin pnpm with a `"packageManager"` field in `package.json`. For any other failure, it says to run the command and read its error.
11+
- **Unchanged.** Each row keeps its status: `pnpm` is `error`, `TypeScript` and `Git` are `warning`. So doctor's exit code does not move. A tool that answers is reported as before. No package export or flag changes, and the report keeps its layout.

0 commit comments

Comments
 (0)