Skip to content

Commit 615683f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-9c0do1
2 parents 6785709 + e1dde1a commit 615683f

70 files changed

Lines changed: 3673 additions & 265 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/core': patch
3+
'@objectstack/service-messaging': patch
4+
---
5+
6+
fix(core, service-messaging): a failed settings read no longer dead-letters a templated mail as `TEMPLATE_INVALID`; only a read that finds no default currency is permanent (#27257)
7+
8+
Clause-②: yes (widening) — `resolveLocalizationContext`'s result (`@objectstack/core`) gains one optional member, `readFailed?: true`. Nothing is narrowed.
9+
Metadata-protocol: none
10+
11+
- **Before.** `resolveLocalizationContext` answered `currency: undefined` both when the organization has no default currency and when its `sys_setting` read failed (the failure is memoized for 30 s). So a messaging delivery whose template has a `{{ amount | currency }}` hole with no argument was refused `TEMPLATE_INVALID` during a transient settings fault, graded permanent and never sent, although a retry would have worked.
12+
- **Now.** The answer carries `readFailed: true` when the direct `sys_setting` read threw, on the fresh read and on a hit of the failure memo alike. A settings-service refusal does not count, and the memo is not lengthened. Messaging's tenant-currency read rejects on it; the email and inbox channels then render without a currency. When that render answers `TEMPLATE_INVALID`, they render the template once more without sending, with the no-currency code `XXX` as the default: if that passes, the missing default was the only refusal, and the delivery is recorded without a `TEMPLATE_*` code and retried; if it still refuses, that refusal stands and is graded permanent, as before. Once the read succeeds, an organization with no default currency gets `TEMPLATE_INVALID` with its remedy, graded permanent, as before.
13+
- **Unchanged.** A template with no no-argument `currency` hole is sent during a settings fault, as before. Every other template failure keeps its grade. The other callers of `resolveLocalizationContext` see the same values; the new member is optional and absent on every successful read.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/plugin-gantt': patch
4+
---
5+
6+
fix(app-shell, plugin-gantt): the home launcher tile draws the app's branding, and a gantt's name column is headed by its object
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The console home's app tiles (and the `app:launcher` block, which draws the same strip) now follow the app's declared branding, the way the top-bar app pill and the app switcher already do: the `branding.logo` image when one is declared, else the app's icon on its `branding.primaryColor` when that is a hex, else the icon on the name-keyed gradient as before. A logo that fails to load falls back to the tile.
12+
13+
A gantt bound to an object now heads its task list's name column with that object's label (translated like every other surface that names the object), so a campaign gantt reads "Campaign" rather than "Task Name". An inline data set, an object that declares no label, and a mixed-object tree (`objectField`) keep the generic heading.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(console): a click on a declared same-origin `type: 'api'` action goes through the platform action door
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The console's two api handlers (list, toolbar and page actions; the record page's header and related lists) fetched an api action's `target` directly, so the click met none of the platform's gates: switching a packaged api action off in Setup did not stop its button, and a human click took a different road from an agent's `run_action`. A click whose object and `name` resolve to a declared `type: 'api'` action whose `target` is a path on this origin now goes to `POST /api/v1/actions/:object/:action` with the subject row's id and the params bag (`{{page.<var>}}` tokens resolved in the browser). The server runs the sign-in floor, `requiredPermissions`, the activation switch, the param contract and the subject-row load, then forwards to the declared target with the body it assembles from the declaration. A switched-off action answers `409 ACTION_DISABLED`, shown as its sentence, and its target is never called.
11+
12+
An api action whose `target` is on another origin, and an inline page button that names no declared action, keep the direct fetch. The entitlement dialog, the flow-backed refusal message, the `{ success, data }` unwrap, the `newTabUrl` fast path and `refreshAfter` read the door's answer as they read the target's.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): a validation rule guard the publish refuses holds the Studio Data pillar's autosave (#27644)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The Data pillar read the object draft and dropped its served `_diagnostics`, so a validation rule guard the publish refuses (an ordering compare on a nullable field, `record.amount > 100`) read clean and autosaved until Publish refused it. The pillar now keeps the draft read's verdict and the body it judged, re-reads both after each save, and hands them to the Validations tab. Each rule's guard carries its own verdict path (`validations.N.condition`, a conditional rule's `.when`), so the error the server located there is shown under the guard and holds the autosave while the guard still shows the judged text, in the raw CEL editor and in the row builder alike. Editing the guard releases the hold: that save goes out and its re-read brings the next verdict. No rule is restated in the console.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): a `user` field over a text column passes the external-datasource type check; ADR-0015 names the `odata` vocabulary
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
- **Type matrix (`@objectstack/spec/data`).** `user` is now exact under the `text` row, as it already was under `uuid`. A `user` field's target is fixed to `sys_user`, so its column holds a platform user id, which is a string: better-auth mints 32 alphanumeric characters, and the SQL driver mints a 16-character nanoid for a row the engine writes without one. Before, `isCompatible` refused a `user` field over a `varchar` / `Edm.String` column as a `type_mismatch` error, so the boot validation (`onMismatch: 'fail'`) refused a federated object that declared one. Nothing that passed before fails now.
11+
- **Integer rows unchanged.** A platform user id is never an integer, so `user` stays refused under `integer` / `bigint` (`Edm.Int32`, `Edm.Int64`), even though `lookup` and `master_detail` are exact there.
12+
- **ADR-0015 §4.6** lists `odata` among the dialects the matrix knows, pointing at `DIALECT_ALIASES.odata`.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): the list-view panel edits a chart view's dataset binding
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
In Studio › Interfaces, the list-view panel of a chart view now has a "Chart data" section. It lists the datasets built on the view's object by label, then the chosen dataset's measures and dimensions by label, and each pick saves to the view's `chart` block (`dataset`, `values`, `dimensions`) as the other settings do. The chart type can be changed there too, among the types the spec lists. Before, a chart view's binding could only be set once, in the Create View dialog.
12+
13+
Picking another dataset keeps only the measures and dimensions that dataset also declares; the others are dropped, since a name the dataset does not declare binds nothing. With no measure left, the edit is held, as an unset required setting is, until one is picked. A stored name the bound dataset does not declare is marked so it can be removed. If the datasets cannot be read, the stored binding is shown as saved with the reason. An object with no dataset says so and offers no picker.
14+
15+
The Create View dialog and the new section read the object's datasets through one shared hook (`useObjectDatasetBinding`); the dialog's behaviour is unchanged.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/service-automation': patch
3+
---
4+
5+
fix(service-automation): a connector definition saved, published or deleted on one replica now converges every replica
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A connector instance definition changed through the per-item publish door re-materialized only on the replica that published it: that replica's `metadata:reloaded` hook ran the connector reconcile, but the hook is kernel-local, and the cluster's metadata-mutation lane (lane 2, `metadata.mutated`) delivered the change to the other replicas' mutation listeners, where the automation plugin acted on `flow` mutations only. Peers kept serving the old definition until a reload or a restart. A direct active save (`PUT /api/v1/meta/connector/:name`) raised no reload at all, so it did not re-materialize even the replica that wrote it.
11+
12+
The automation plugin's metadata-mutation listener now queues the same soft reconcile `metadata:reloaded` runs for a `connector` mutation that is not a draft. No new cluster channel is added: a peer converges on the lane 2 replay it already receives, re-reading the shared store.
13+
14+
- A publish on the writing replica reconciles from both of its signals; the second run finds the instance unchanged, so the provider is still called once per changed definition.
15+
- A direct active save re-materializes the writing replica and converges its peers. A deleted connector is torn down on every replica.
16+
- A draft save, and a mutation of any other type, runs no connector reconcile. Flow mutations are handled as before.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
107107
| `pm:dispatched` | 已派发(派发评论记轮次),恒带 assignee;与摘 `pm:queue` 同一次标签写入成对落地 |
108108
| `needs-user-decision` | 决定待做:永不派发、永不代答;维护者的收件箱,只由人工裁 |
109109
| `pm:on-hold` | 决定已做且答案是暂不做:不派发不催;仅当带机器可读 `Restart-when:` 行才合法 |
110-
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描或 `scheduled-full-run-card.yml` 刷新放回;工已完、PR 被外部门禁卡住的同用本态;正文或线程行首 `Blocked-by:` 指向无 `Ruled:` 的 open `needs-user-decision` 卡 ⇒ ⛔ 不派发,挂本态直至其 `Ruled:` |
110+
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关单(PR 则合并,关而未合并不放)时由解锁扫描或 `scheduled-full-run-card.yml` 放回;工已完而被门禁卡住的同用本态;双通道行首 `Blocked-by:` 指向无 `Ruled:` 的 open `needs-user-decision` 卡 ⇒ ⛔ 不派发,直至其 `Ruled:` |
111111
| `pm:awaiting-maintainer` | 决定已做,只剩一次 GitHub 之外的人工动作:不派发不催;与其它 pm 状态标签互斥;入态恒带行首 `Maintainer-action:` 行(细则见 `references/state-machine.md`),无行即半态 |
112112
| `pm:blocking` | 有 open 下游依赖者(自 `Blocked-by:` 索引推导的缓存,⛔ 不手工挂);进选择全序 |
113113
| `pm:retriage` | 向分诊提问(改判、跨域 PR 指定车道、改路由、拆卡、裁 dev 报告留下的分叉;既裁冲突砍项与整树席改标归执行席 ⛔ 不问),定车道与改路由限未派发卡,异议评论写明所求;与现行 `pm:*` 并存、⛔ 不摘原标;带本标签的 `pm:queue` 卡跳过派发 |

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@
249249
- 评论里独立成行的标记同被读路径吃掉留空行 ⇒ 缺失不证写侧剥除,先读 payload 原体。
250250
- `Blocked-by:` 行写入归 BODY,唯 full-run 卡写评论(其正文每红重写);追加按解码后写回执行。
251251
- 评论里的旧行按同程序增量回填,⛔ 不搞批量突击;半状态巡查与 full-run 刷新读双通道。
252-
- 解锁扫描 `list_issues` 列卡、`issue_read` 直读目标,⛔ 不加常设评论读;PR 判合并读 `merged_at`。
252+
- 解锁扫描 `list_issues` 列卡、逐目标直读,⛔ 不加常设评论读;PR 合并走 `pull_request_read`/REST。
253253
- 动手前按空的行内代码跨度判 MCP 读可用否;命中即改从原始体取,⛔ 不拿有损读数回写。
254254
- `list_issue_types` 对本集成 403 而 `issue_write type:` 正常,是读权限缺口。
255255
- ⇒ 直接写已知好值(`Bug`/`Feature`/`Task`),写侧报错才是真信号;列表 403 不等于类型不可用。

‎docs/adr/0015-external-datasource-federation.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (one Zod source of truth per metadata type)
66
**Consumers**: `@objectstack/spec`, `@objectstack/runtime`, `@objectstack/objectql`, `@objectstack/plugins/driver-sql`, `@objectstack/services/service-ai`, `@objectstack/cli`, `../objectui`
7-
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27), attribution only — see the note below. D7 (2026-10-29, ruling 6486136301 on #27298) — an API-backed datasource may borrow a declared connector instance's credential through `external.connector`; see the D7 note below. #27688 (2026-10-30), example only — §8's `credentialsRef` is a `sys_secret:` handle, the only form the shipped secret binder resolves; see the note under §8.
7+
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27), attribution only — see the note below. D7 (2026-10-29, ruling 6486136301 on #27298) — an API-backed datasource may borrow a declared connector instance's credential through `external.connector`; see the D7 note below. #27688 (2026-10-30), example only — §8's `credentialsRef` is a `sys_secret:` handle, the only form the shipped secret binder resolves; see the note under §8. #27917 (2026-10-30), dialect list only — §4.6 names the `odata` vocabulary the matrix gained with #27745.
88

99
> **Amendment note D7 (2026-10-29, ruling 6486136301, letter A) — a second credential channel.**
1010
> §10 says credentials reach a datasource only through `credentialsRef`. That stays true for a
@@ -410,8 +410,13 @@ A pure module `packages/spec/src/data/type-compat.ts` maps SQL types
410410
(`text`, `varchar`, `numeric(10,2)`, `timestamptz`, `jsonb`, …) to
411411
ObjectStack field types, and exposes `isCompatible(remoteSqlType,
412412
fieldType): boolean | 'lossy'`. The matrix is dialect-aware
413-
(`postgres`, `mysql`, `sqlite`, `snowflake`, `bigquery`, `mongo`). New
414-
dialects extend the matrix; the matrix is unit-tested independently.
413+
(`postgres`, `mysql`, `sqlite`, `snowflake`, `bigquery`, `mongo`, `odata`). New
414+
dialects extend the matrix; the matrix is unit-tested independently. `mongo` and
415+
`odata` are the two vocabularies that are not SQL: `DIALECT_ALIASES.mongo` maps
416+
BSON type names, and `DIALECT_ALIASES.odata` maps the EDM primitive types an
417+
OData service's `$metadata` declares, except `Edm.Duration` and the
418+
`Edm.Geography*` / `Edm.Geometry*` families, which it leaves unmapped so a draft
419+
flags them for review.
415420

416421
---
417422

0 commit comments

Comments
 (0)