Skip to content

Commit 07aba47

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24772-default-datasource-status
2 parents 9e06f3d + 5fcc794 commit 07aba47

25 files changed

Lines changed: 1071 additions & 77 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-sharing': patch
3+
---
4+
5+
fix(plugin-sharing): a record share's Recipient column names the recipient instead of drawing its raw id
6+
7+
Clause-②: no
8+
9+
- **`sys_record_share.recipient_id` declares `widget: 'recipient-picker'` and `dependsOn: ['recipient_type']`**, the display declaration `sys_sharing_rule.recipient_id` already carries. The console draws a field with this widget by naming the stored id through the row's `recipient_type`, so a user grant reads as the user's name in a list that shows the Recipient Type column beside it (Setup → Record Shares → All). A list that does not fetch `recipient_type` still draws the stored id.
10+
- **`object_name` is not a dependency here**: the face reads it only for the `field` recipient kind, which a record share cannot store.
11+
- **Unchanged:** `recipient_id` stays the same required text column, the object stays engine-owned and read-only through the data API, and `grant()` stays the only writer of a share row.
12+
13+
A patch: one display hint on one field of a shipped platform object. No export, type, accepted value, stored column or authorable key changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
Field and object designer forms: the `expression` help text now states what the engine does instead of claiming the expression "makes it read-only". On a `formula` field the expression is computed on read with no stored column; on any other field the engine computes it from the row each write stores and stores the result, so a client-sent value never stands (only `readonly: true` makes a client value a readonly drop). The object designer's field grid now shows the `expression` row for every field type, as the field designer already did, since `FieldSchema` accepts `expression` on any type. The zh-CN, ja-JP and es-ES translations of both rows are updated to match.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): the delegable-scope report no longer offers the built-in identity positions to a platform administrator or a delegate
6+
7+
Clause-②: no
8+
9+
`GET /security/my-delegable-scope` (`describeDelegableScope`) listed `platform_admin`, `org_owner`, `org_admin` and `org_member` in `assignablePositions` for a tenant-level administrator and for a delegate holding a placing `adminScope`. `sys_user_position.position` refuses those four names from every writer with `400 VALIDATION_FAILED`, so the invite dialog and Setup's position picker offered choices that could only fail.
10+
11+
Every branch of the report (tenant-level, organization-level, delegated) now reads one position listing that leaves out the ADR-0068 D2 identity names through the same `isReservedIdentityName` predicate the write's validation is built from, beside the audience anchors it already left out. No other position leaves any branch, and placeable business units are unchanged. The write path's refusal and the reserved list are unchanged. No spec key, endpoint or configuration changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
fix(service-settings): Setup's AI "Test embedder" and Knowledge "Test connection" buttons are answered, and their answers say they checked the form only
6+
7+
Clause-②: no
8+
9+
- **Before.** Both buttons answered `400 SETTINGS_ACTION_FAILED` with "No handler registered for action 'test_embedder' in 'ai'." and "No handler registered for action 'test' in 'knowledge'." The manifests declared both buttons and the package exported both handlers (`aiTestEmbedderActionHandler`, `knowledgeTestActionHandler`), but `SettingsServicePlugin`'s default `actionHandlers` map did not register them.
10+
- **Now.** The default map registers both, beside `mail.test`, `sms.test`, `storage.test` and `ai.test`. A host that passes its own `actionHandlers` still replaces the whole default map, and a plugin can still replace any one handler with `SettingsService.registerAction`.
11+
- **What each button checks.** Both are form checks and send nothing over the network. "Test embedder" checks that a provider is chosen, that an API key is set (Ollama excepted) and that `custom` / `azure` have a Base URL. "Test connection" checks that a managed `libsql://` Turso URL has an auth token and that RAGFlow has a Base URL and an API key. The in-memory adapter answers with a warning that there is no external service to probe.
12+
- **Answer text.** A passing "Test embedder" now ends "This checks the form only — no embedding request was sent." Every passing "Test connection" on Turso or RAGFlow now says "This checks the form only — no connection was attempted." The old "Mount @objectstack/embedder-openai + a knowledge adapter to exercise live calls" and "Mount @objectstack/knowledge-ragflow to exercise live calls" are gone: neither package registers these actions, so mounting it never made either check live.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/rest': minor
3+
---
4+
5+
fix(rest)!: `POST /batch` answers an update or delete op whose id names no row with the missing-id `400`, not a `500` (#24781)
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) A refusal at the cross-object batch door of request shapes that never wrote anything: every one of them already failed, as a 500 (or, for a delete addressed only by data.id, a 400 INVALID_FILTER), and now fails up front with the 400 a missing id already got. No metadata key, spelling, export, type or stored shape moves, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone; the remedy is sending the record's id, stated below. -->
10+
11+
**BREAKING** accept-set narrowing on one REST door, shipped as `minor` under the launch-window convention for accept-set narrowings. No request that succeeded before is refused now.
12+
13+
The cross-object batch checked up front that an `update` / `delete` op names a row, but tested only for an absent or `null` id. An id the engine does not take as one reached the engine, and its `Update requires an ID…` / `Delete requires an ID…` throw answered as a server fault. A client's retry policy keyed on `5xx` then retried a request that can never succeed. The check now judges the id the op's write arm actually sends, with the engine's own by-id rule (a truthy string or number).
14+
15+
- FROM: `update` with `"id": ""`, or with no `id` and a `data.id` of `""`, `0`, `true`, `false`, an object or an array, answered `500 DATABASE_ERROR`. An `"id": ""` beside a real `data.id` did too, because `id` is read first.
16+
- FROM: `delete` with `"id": ""` answered `500 INTERNAL_ERROR`.
17+
- FROM: `delete` with no `id` and the record's id in `data.id` answered `400 INVALID_FILTER`, with a message about a `where.id` the caller never wrote. The delete arm never read `data.id`; it is the create/update payload.
18+
- TO: each of these answers `400 VALIDATION_FAILED` with `Operation 'update' on 'OBJECT' requires an id` (or `'delete'`), the answer the same op with no id already got. The batch is refused before the transaction opens, and nothing is written.
19+
- Fix: send the record's id in the op's `id` (an update may carry it in `data.id` instead).
20+
21+
This step runs before the per-object API-exposure check, as it did for a missing id. So for such an op, this `400` now also replaces whatever a later step used to answer first, such as the `404` / `405` for an API-disabled object or method.
22+
23+
**Unchanged.** A missing or `null` id answers the same `400`. A real id updates and deletes as before, by `id` or (update only) by `data.id`. An update whose `data.id` is a `{ $ref }` to an earlier create still resolves. A whitespace id is an id the engine looks up: it answers `404 RECORD_NOT_FOUND`, like any unknown id. The per-object doors (`/data/:object/batch`, `updateMany`, `deleteMany`) already refused a blank id per row and are not changed.

‎packages/lint/src/validate-predicate-path-refs.test.ts‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,12 @@ describe('#7010 corpus — shipped METADATA_FORM_REGISTRY', () => {
682682
// It is 89 today, an ADDITION of ONE: #23735 offered the field form's
683683
// `referenceDeletedWhen` composite row, gated like `referenceVia` on
684684
// `data.type == 'text'`. It reaches the literal-comparison census below.
685-
expect(predicates, 'the shipped metadata forms carry no predicates at all').toBe(89);
685+
// It is 88 today, a REMOVAL of ONE: #24735 dropped the object form's
686+
// `object :: expression :: data.type == 'formula'` gate. `FieldSchema`
687+
// accepts `expression` on every type and the engine computes and stores it
688+
// on a non-formula field, so the formula-only row hid a live key; the row
689+
// is now shown for every type, as the field form's own row already was.
690+
expect(predicates, 'the shipped metadata forms carry no predicates at all').toBe(88);
686691

687692
const findings = validatePredicatePathRefs(corrupted);
688693
expect(findings).toHaveLength(predicates);
@@ -793,7 +798,9 @@ describe('#7010 corpus — shipped METADATA_FORM_REGISTRY', () => {
793798
// `data.type == 'email'`, a single-quoted literal comparison.
794799
// It is 66 today: #23735's `field :: referenceDeletedWhen` row is gated on
795800
// `data.type == 'text'`, a single-quoted literal comparison.
796-
expect(comparisons, 'no shipped predicate carries an `==`/`!=` literal comparison').toBe(66);
801+
// It is 65 today: #24735 removed the object form's `expression` gate,
802+
// `data.type == 'formula'`, one single-quoted literal comparison.
803+
expect(comparisons, 'no shipped predicate carries an `==`/`!=` literal comparison').toBe(65);
797804

798805
const rhsFindings = validatePredicatePathRefs(corrupted)
799806
.filter((f) => f.rule === PREDICATE_RHS_PATH_SHAPED);
@@ -822,7 +829,9 @@ describe('#7010 corpus — shipped METADATA_FORM_REGISTRY', () => {
822829
// tree adds over its merge base — the two corpora were enumerated and
823830
// differenced by `<form>::<field>::<source>`, and the object-form half of
824831
// the two-entry delta is exactly it. #6254's own measurement was 16 and
825-
// stays 16 — that number is history, this one is a census.
832+
// stays 16 — that number is history, this one is a census. It is 18 today:
833+
// #24735 removed the `expression` row's `data.type == 'formula'` gate, so
834+
// that row is shown for every field type.
826835
const objectForm = structuredClone(METADATA_FORM_REGISTRY.object) as Record<string, unknown>;
827836
let restored = 0;
828837
const debare = (node: unknown): void => {
@@ -852,10 +861,10 @@ describe('#7010 corpus — shipped METADATA_FORM_REGISTRY', () => {
852861
expect(
853862
restored,
854863
"the object form's `data.type`-rooted predicates are no longer where this test looks",
855-
).toBe(19);
864+
).toBe(18);
856865

857866
const findings = validatePredicatePathRefs({ views: [objectForm] });
858-
expect(findings).toHaveLength(19);
867+
expect(findings).toHaveLength(18);
859868
expect(new Set(findings.map((f) => f.rule))).toEqual(new Set([PREDICATE_PATH_UNROOTED]));
860869
expect(findings[0].message).toContain('`type`');
861870
});

‎packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
226226
},
227227
"fields.expression": {
228228
label: "Expression",
229-
helpText: "CEL formula expression"
229+
helpText: "CEL expression the field's value is computed from. On a formula field: computed on read, no stored column. On any other field: computed by the engine from the row each write stores, and stored, so a client-sent value never stands."
230230
},
231231
"fields.returnType": {
232232
label: "Return Type",
@@ -763,7 +763,7 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
763763
},
764764
expression: {
765765
label: "Expression",
766-
helpText: "CEL expression to calculate this field (makes it read-only)"
766+
helpText: "CEL expression the field's value is computed from. On a formula field: computed on read, no stored column. On any other field: computed by the engine from the row each write stores, and stored, so a client-sent value never stands."
767767
},
768768
returnType: {
769769
label: "Return Type",

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
226226
},
227227
"fields.expression": {
228228
label: "Expresión",
229-
helpText: "Expresión de fórmula CEL"
229+
helpText: "Expresión CEL con la que se calcula el valor del campo. En un campo de fórmula: se calcula al leer, sin columna almacenada. En cualquier otro campo: el motor lo calcula a partir de la fila que guarda cada escritura y lo almacena, por lo que un valor enviado por el cliente nunca prevalece."
230230
},
231231
"fields.returnType": {
232232
label: "Tipo de retorno",
@@ -763,7 +763,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
763763
},
764764
expression: {
765765
label: "Expresión",
766-
helpText: "Expresión CEL para calcular este campo (lo hace de solo lectura)"
766+
helpText: "Expresión CEL con la que se calcula el valor del campo. En un campo de fórmula: se calcula al leer, sin columna almacenada. En cualquier otro campo: el motor lo calcula a partir de la fila que guarda cada escritura y lo almacena, por lo que un valor enviado por el cliente nunca prevalece."
767767
},
768768
returnType: {
769769
label: "Tipo de retorno",

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
226226
},
227227
"fields.expression": {
228228
label: "式",
229-
helpText: "CEL 数式"
229+
helpText: "このフィールドの値を計算する CEL 式。数式フィールドでは読み取り時に計算され、列は保存されません。その他のフィールドでは、書き込みごとに保存される行からエンジンが計算して保存するため、クライアントが送信した値は反映されません。"
230230
},
231231
"fields.returnType": {
232232
label: "戻り値の型",
@@ -763,7 +763,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
763763
},
764764
expression: {
765765
label: "式",
766-
helpText: "このフィールドを計算する CEL 式(読み取り専用化)"
766+
helpText: "このフィールドの値を計算する CEL 式。数式フィールドでは読み取り時に計算され、列は保存されません。その他のフィールドでは、書き込みごとに保存される行からエンジンが計算して保存するため、クライアントが送信した値は反映されません。"
767767
},
768768
returnType: {
769769
label: "数式の戻り値型",

‎packages/platform-objects/src/apps/translations/object-field-editor-panel-echo-decisions.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,10 @@ const DECISIONS: readonly Decision[] = [
217217
type: 'object',
218218
key: 'fields.expression',
219219
prop: 'helpText',
220-
en: 'CEL formula expression',
220+
en: "CEL expression the field's value is computed from. On a formula field: computed on read, no stored column. On any other field: computed by the engine from the row each write stores, and stored, so a client-sent value never stands.",
221221
verdict: ALL_TRANSLATE,
222222
reason:
223-
'CEL is a language name and stays verbatim; the rest is prose. The formula vocabulary already exists on this very panel — returnType.helpText ("Result type for formulas") is authored in all three locales — so this leaf was simply never reached.',
223+
'CEL is a language name and stays verbatim; the rest is prose. The formula vocabulary already exists on this very panel — returnType.helpText ("Result type for formulas") is authored in all three locales — so this leaf was simply never reached. Re-translated when the row widened past `formula` to every type: the sentence is the field designer\'s own expression.helpText, authored in the same words in all three locales.',
224224
},
225225
{
226226
type: 'object',

0 commit comments

Comments
 (0)