Skip to content

Commit 8bdde03

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-xqgh8b
2 parents 6f6d2f3 + 7a48bf7 commit 8bdde03

182 files changed

Lines changed: 3585 additions & 583 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-hono-server': patch
4+
'@objectstack/plugin-sharing': patch
5+
'@object-ui/permissions': patch
6+
---
7+
8+
feat(spec): a strict response contract for `GET /auth/me/permissions`, and `hasAnyQueue` on the ownership-queue contract
9+
10+
Clause-②: yes (widening)
11+
Metadata-protocol: added
12+
13+
`GET /auth/me/permissions` is the payload the console reads once per session for its whole permission layer. Until now nothing in `@objectstack/spec` declared its body. It is now declared, strictly:
14+
15+
- **`MePermissionsResponseSchema`** (`@objectstack/spec/api`), discriminated by `authenticated`:
16+
- `MePermissionsAnonymousResponseSchema`: `{ authenticated: false }` and nothing else.
17+
- `MePermissionsAuthenticatedResponseSchema`: `userId`, `tenantId` (nullable), `positions`, `permissionSets`, `objects` (effective object permissions), `fields` (`{ readable, editable }` per `object.field`), `systemPermissions` and `tabPermissions` (optional: absent only on the body a server without SecurityPlugin answers, where absent means unknown), and `hasAnyQueue: boolean`.
18+
- Both branches are strict: an undeclared key is refused, not dropped. A key the server starts sending is declared here first.
19+
- Types `MePermissionsResponse`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`.
20+
- **`IOwnershipQueueService.hasAnyQueue?(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. Optional, like `declaredQueueNamed?`, so an implementation written before it still satisfies the contract; the route answers `false` without it. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it.
21+
- **`@objectstack/plugin-hono-server`** types the route's body builder against `MePermissionsAuthenticatedResponse`, and reads `hasAnyQueue` through the contract. The wire is unchanged.
22+
- **`@object-ui/permissions`**: `MePermissionsResponse` is now the spec's type, re-exported from `@objectstack/spec/api` (with `MePermissionsAuthenticatedResponse` beside it), in place of a hand-written interface that had drifted from the wire. The interface read a `roles` key the server never sends; the server's key is `positions`, so `usePermissions().roles` under `MePermissionsProvider` was always empty and now carries the caller's positions. A fixture typed as the response migrates `roles: [...]` → `positions: [...]` and adds `hasAnyQueue`; the anonymous body is `{ authenticated: false }` and nothing else.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): edit an email template or a translation in the All metadata area, and customize a read-only package's one into its pair
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Until now an admin changed an email template or a translation in Setup, which writes a row bound to no package. Studio's All metadata area now opens both in a simplified form editor, and a change to an installed package's item is a declared override in the package's paired customization package (`<base>.custom`, ADR-0146 D2).
12+
13+
- **The editor.** In a package's All metadata area (`/studio/<package>/metadata/<type>/<name>`), an `email_template` or a `translation` opens the fields Setup's form offers, beside the type's preview (the sandboxed email preview, the translation coverage). Both are Setup's components, mounted, not copied: the form is the served schema, else the built-in fallback, as on the generic item page. The cross-package leg, the environment scope and `?review=1` keep opening the generic item page. Setup's pages are unchanged.
14+
- **A writable package.** An edit saves that package's draft through the shared Studio autosave, with the same version pin as the pillars. The header's Publish puts it live.
15+
- **A read-only package.** The editor offers Customize, the same control as a view, dashboard or report (the Interfaces pillar). The first saved edit calls the ensure door once and saves the draft into the pair it answers, declaring `overrides: { package, basedOn }`. The badge reads the served declaration. "Restore package original" in the ⋯ menu deletes the pair's draft, then its row. A walled caller sees Customize disabled, with the reason.
16+
- **One email template locale at a time.** An email template is one item per `(name, locale)`. The editor opens the locale the list row names (`?member=`). It reads, saves, declares, badges and restores that locale alone, and keeps the locale out of the form, so an edit never lands in another locale. The other locales of the template are untouched. Before the first save on a read-only package, the editor says only that locale is customized.
17+
- **A translation is replaced whole.** A translation's override is its whole locale bundle (ADR-0146 D2). Before the first save on a read-only package, the editor says so.
18+
- **New strings.** `@object-ui/i18n`'s studio namespace gains three `engine.studio.area.formEditor.*` keys in `en` and `zh`. The other languages fall back to `en`, as the whole namespace does.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@objectstack/formula": minor
3+
"@objectstack/lint": minor
4+
"@objectstack/objectql": patch
5+
---
6+
7+
fix(formula,lint)!: a formula whose `label()` names a field that declares no options is refused at author time
8+
9+
`label(record.FIELD)` answers on a field whose definition carries an `options` list; on any other field the evaluator faults on every row, so the formula read `null` forever with a WARN line as the only trace. The `expression-invalid` gate refused `label(record.nope)` (unknown field) and `sqrt(4)` (unregistered function) but admitted `label(record.first)` on a text field. It now refuses that too, at `os validate`, `os build`, `os lint` and every door that runs the active authoring gate (the object save door, per-item publish, history restore and a draft's `_diagnostics`; one evaluation, `runRuntimeAuthoringRules`), in the evaluator's own words (the same sentence its WARN line carries), naming the field and the remedy.
10+
11+
Clause-②: no (narrowing) — the object save door and the CLI authoring commands refuse a formula whose `label()` argument names a declared field that declares no options, which they stored before. Nothing is widened.
12+
13+
Metadata-protocol: narrowed (a formula field's `expression` may no longer call `label()` on a field that declares no `options`)
14+
15+
<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed. The refused value never worked: the evaluator faults on that call on every row, so a stored formula of this shape reads null for every reader today, and no conversion could give it a label to read. The refusal names the field and the remedy (read the field directly, or point label() at a field that declares options). -->
16+
17+
**BREAKING** accept-set narrowing at the authoring gate (`@objectstack/formula`, `@objectstack/lint`), shipped as `minor`.
18+
19+
- **Before.** `PUT /api/v1/meta/object/NAME` with a formula `label(record.first)` (`first` a text field), or `label(record.kind) + label(record.first)`, answered 200; every row then read the formula as `null`.
20+
- **Now.** The same write answers 422 `expression-invalid` naming `first`, located at the formula field's `expression`; `os validate` / `os build` / `os lint` report the same finding.
21+
- **Unchanged.** `label()` on a field that declares options is admitted and evaluates as before: inline `options` on any type (`select`, `multiselect`, `radio`, …), or a `picklist` naming a shared list, which the registry resolves onto the served field. The unknown-field and unregistered-function refusals are unchanged and an unknown field gets no second refusal.
22+
- **Who sees it.** An author whose formula calls `label()` on a field without options, a formula that already reads null on every row.
23+
24+
**The remedy.** Read the field directly (`record.first`), or point `label()` at a field that declares `options` or names a `picklist`.
25+
26+
`validateExpression` gains an opt-in `ExprSchemaHint.optionFields` (the object's option-bearing fields) and the closed refusal-code set gains `label-field-no-options` (`params: { field }`). `@objectstack/formula` exports the two readings of "declares options" side by side: `declaresOptions(def)` on the served form (`@objectstack/objectql` builds `label()`'s map through it) and `declaresOptionsOnceServed(def)` on the authored form (inline `options`, or a `picklist` the registry writes onto the served field), which `@objectstack/lint`'s formula pass hands the validator, so the gate refuses exactly the fields the evaluator faults on.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol)!: every metadata read states a stored row's package from its column, never from its body
6+
7+
The list read serves a stored row's `_packageId` from its `package_id` column only (#28594); the other reads of a stored row still stamped the column's package only while the body carried none, so a body stamp the save door would strip was echoed back. Now one rule serves them all: the by-name active and strict draft reads, the by-name draft preview, the draft list, the list merge and the registry hydration (with the items a view container row expands to) state the column's package, replacing a body stamp that names another package and dropping one on a row bound to no package.
8+
9+
Clause-②: no (narrowing) — a stored row whose body carries a `_packageId` other than its column's is served and registered under its column's package (or none) at every read, no longer under the body's. Nothing is widened.
10+
11+
Metadata-protocol: none
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed and no stored body is rewritten or refused: the row's package binding is its `package_id` column, which every read already served whenever the body carried no stamp; a body stamp that disagrees with the column was never written by the save door, so no author has anything to convert. -->
14+
15+
**BREAKING** read narrowing (`@objectstack/metadata-protocol`), shipped as `patch`.
16+
17+
- **Before.** A by-name read, a draft read or preview, the draft list and the registry hydration served a stored row whose body carried `_packageId` with the body's value, whatever its column said.
18+
- **Now.** Each serves it with its column's package, or with none when the row is bound to no package, as the list read does.
19+
- **Unchanged.** A row whose body carries no stamp, or one equal to its column, is served exactly as before.
20+
- **Who sees it.** Only a row written before the save door began stripping body stamps, or by a writer other than the save door.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics): the draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do, and the ObjectQL strategy serves a backed `RELATION.id` dimension
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
Three answers `queryDataset` gave differently depending on the face, measured on real SQLite:
11+
12+
- **A bucketed time dimension in the draft preview.** A `timeDimensions` entry with a `granularity` is now grouped by its bucket whether or not the member is also in `dimensions`, and the bucket is a column of the answer and of `fields` (type `time` for a `date` dimension). This is the set both live strategies project. Before, the preview grouped by `dimensions` alone: one total where the live faces answered one row per month, regions without months, and a windowed `year` bucket with no key.
13+
- **A filter member spelled other than its column, in the draft preview.** A `runtimeFilter` (or dataset filter) member such as `OBJECT.region`, `CUBE.region` or a dataset dimension `zone` declared over `region` is now read from the column it names, with that column's declared type, through the member resolution the live strategies use. Before, the preview matched no drafted row: `[]`, or under an `$or` only the other arm's rows, where both live faces served the rows.
14+
- **A backed `RELATION.id` dimension on the ObjectQL strategy.** `account.id` (with `include: ['account']`), and two dimensions through one relationship (`account.id` beside `account.name`, or the FK column `account` beside `account.name`), are now served with the native strategy's groups. Before, the strategy handed the engine a `groupBy` naming one column twice and answered `INVALID_QUERY` / 400. The engine's duplicate-column refusal is unchanged; the strategy now names each column once.
15+
16+
Nothing is refused that was served, and no export, key, error code or status is added. The draft preview still refuses a relationship-path member (#28555).

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
6969
- 批 = 一个上下文里读完的 ≤5 张同族卡,是质量单位,⛔ 不是预算。
7070
- 班 = 一个会话的任期,只以交接或惰性回收结束。
7171
- 席位两形:Routine 席与项目席(会话是车队项目的线程);细则见 `references/seat-lifecycle.md`。
72-
- 项目席唤醒 = PR 订阅 + 每项目一个自建自绑巡查 routine(总监席除外);⛔ 无 `send_later` 链。
72+
- 项目席唤醒 = PR 订阅 + 每席一个自建自绑巡查 routine(总监席除外);⛔ 无 `send_later` 链。
7373
- 探针先行:fire 的第一动作是一次最便宜的车道盘点查询,⛔ 在它之前不读任何长文本。
7474
- 队列空 + 无在飞 + 无待复核 + 决策箱无新答复 ⇒ 自退。
7575
- 分诊席与车道席常驻,断粮即换账号重开该席与信箱;分诊席默认判断档;裁决归总监席。

‎content/docs/data-modeling/formulas.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,10 @@ then a shared picklist's `picklists.<picklist>.options.<value>`), exactly as
298298
`Accept-Language`, so the write response the save toast reads, the record
299299
page title, related lists and lookups all follow it.
300300

301-
- `label()` takes exactly `record.<field>`, a field that declares `options`.
301+
- `label()` takes exactly `record.<field>`, a field that declares `options`
302+
or names a `picklist`. Any other field is refused when the object is saved
303+
(`os validate`, `os build`, the object save door), with the field named,
304+
because it has no label to read and the formula would read null on every row.
302305
An empty value reads `''`; a stored value no option declares reads as itself.
303306
A multi-value select is refused.
304307
- A read with no locale (a script, a scheduled job) reads the declared labels.

0 commit comments

Comments
 (0)