Skip to content

Commit 3f04807

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-5276-loader-delete-contract
2 parents faf012f + fd0069c commit 3f04807

246 files changed

Lines changed: 10205 additions & 280 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.

.changeset/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@objectstack/driver-sql",
3535
"@objectstack/driver-mongodb",
3636
"@objectstack/driver-sqlite-wasm",
37+
"@objectstack/driver-turso",
3738
"@objectstack/plugin-approvals",
3839
"@objectstack/plugin-audit",
3940
"@objectstack/plugin-auth",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): `callData('delete', …)` 的 ObjectQL 兜底返回 spec 声明的 `{ object, id, success }`,与 protocol 路径同形 (#5581)
6+
7+
`callData` 是 protocol 优先 + ObjectQL 兜底,两条路径此前对「删除成功」给的是两种形状:
8+
9+
| 路径 | 此前 | 现在 |
10+
|---|---|---|
11+
| protocol(`deleteData`) | `{ object, id, success: true }` | 不变 |
12+
| ObjectQL 兜底 | `{ object, id, deleted: true }` | `{ object, id, success: true }` |
13+
14+
规范只有一个:`DeleteDataResponseSchema`(`packages/spec/src/api/protocol.zod.ts`)声明的是
15+
`{ object, id, success }`,`deleted` 从未被任何 schema 声明;公开的 HTTP 文档
16+
(`content/docs/protocol/kernel/http-protocol.mdx`)也一直写的是 `success`。所以兜底是唯一
17+
的偏离方,protocol 路径与 spec、与文档都无需改动。
18+
19+
这是 #5138 同一族缺陷的成功侧:#5138 收敛的是「记录不存在」的答案,本次收敛的是「删除成功」
20+
的答案 —— 后者是每一次正常请求都会走到的面,而非只在 id 写错时才碰到。此前按
21+
`DeleteDataResponseSchema` 写的客户端,在**未注册 `protocol`**的精简装配上会从一个 HTTP 200
22+
里读到 `success === undefined`,即「删除到底成没成功」读不出来,而调用方无从分辨自己走的是哪条
23+
路径。消费端各自兼容 `success ?? deleted` 两种拼写正是 contract-first 禁止的形状,所以修在
24+
生产方,不在消费方。
25+
26+
## ⚠️ 升级须知(行为变化)
27+
28+
**仅影响没有安装 `MetadataPlugin`(`@objectstack/metadata-protocol`,即注册 `protocol` 槽)的
29+
精简装配。** 装了该插件的部署走 protocol 优先路径,本来就返回 `success`,不受影响。
30+
31+
在这类精简装配上,以下三个面的 `DELETE` 成功体键名由 `deleted` 改为 `success`:
32+
33+
- `DELETE /api/v1/data/:object/:id`
34+
- MCP 的 `delete_record` 工具(`domains/mcp.ts``remove` 桥)
35+
- 声明式端点(`objectParams.operation: 'delete'`,#5092)
36+
37+
若你的代码读的是 `response.data.deleted`,请改读 `response.data.success` —— 这也是 spec 与
38+
公开文档自始至终声明的键。删除行为本身(含 #5138 落的「记录不存在则 404 `RECORD_NOT_FOUND`
39+
且不发出写」)完全未变,变的只有成功体拼写这一个键。
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
`os dev --fresh` states the isolation it actually delivers (#5594)
6+
7+
The `--fresh` block promised its tempdir "owns ALL persistent state for this
8+
run". After #4968 that is true of everything the CLI itself places — the dev
9+
SQLite DB (`OS_HOME``<home>/data/dev.db`, published as `OS_DATABASE_URL`),
10+
the uploads root (published on the settings service's own name
11+
`OS_STORAGE_LOCAL_ROOT`), and any plugin state keyed off `OS_HOME` — but it was
12+
never true of state an **app** reaches by a relative path it declares itself.
13+
Such a path is resolved by its own consumer against the process working
14+
directory, which `--fresh` does not move, so the file lands in the project tree
15+
and is still there after the run exits.
16+
17+
The live specimen is deliberate authoring, not a bug: the showcase's
18+
`showcase-external` datasource declares
19+
`filename: '.objectstack/data/showcase_external.db'` and documents that the path
20+
resolves against the project cwd — so a `--fresh` showcase run leaves that file
21+
(plus `-wal`/`-shm`) behind.
22+
23+
No behaviour changed. The `--fresh` flag help, the source comments, and the
24+
`os dev` flag table in the CLI docs now name the covered surface
25+
(`OS_HOME`-keyed state plus the env channels the CLI publishes) and state
26+
plainly what falls outside it, with a docs note on declaring an absolute path
27+
when a datasource should follow `--fresh`.
28+
29+
Re-anchoring app-declared relative paths on `OS_HOME` is a behaviour change
30+
resting on an open contract question ("relative to cwd" vs "relative to this
31+
run's home") and is deliberately not taken here.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
"@objectstack/driver-turso": minor
3+
"@objectstack/driver-memory": patch
4+
"@objectstack/driver-mongodb": patch
5+
"@objectstack/driver-sql": patch
6+
"@objectstack/driver-sqlite-wasm": patch
7+
---
8+
9+
feat(drivers): `@objectstack/driver-turso` 迁回本仓并公开发布,五个 driver 统一收进 `packages/drivers/` (#4645)
10+
11+
`TursoDriver` 一直以 `extends SqlDriver` 的方式**跨仓库继承**本仓的类,自己却住在闭源的
12+
`objectstack-ai/cloud``publishConfig: restricted`)。而本仓的 runtime 早就把 turso 当一等
13+
公民——`http-dispatcher.ts` 里环境 provisioning 的偏好顺序第一位就是它,`POST /cloud/environments`
14+
`driver` 参数示例是 `memory | turso``objectql/src/engine.ts` 还带着一段 turso 专属的瞬时
15+
`fetch failed` 重试。开源侧的代码路径引用着一个自己仓里既测不到也 grep 不到的 driver,闭源侧则
16+
在每次 pin bump 时追赶父类的重构。维护者裁定把核心迁回本仓、公开 Apache-2.0 发布。
17+
18+
**新包 `@objectstack/driver-turso``packages/drivers/driver-turso`,Apache-2.0,`access: public`**
19+
带着它在 cloud 的全部实现与测试落地:`TursoDriver`(local / replica / remote 三种传输模式)、
20+
`RemoteTransport`(纯 `@libsql/client` 走 HTTP/WebSocket,无原生依赖,可跑 serverless/edge)、
21+
驱动的 spec/Studio 元数据,以及 15 个测试文件 538 条断言——全部 hermetic,默认 CI 下不碰网络、
22+
不要凭据(remote 面走包内的 sqlite stub)。
23+
24+
**留在 cloud(不随迁)**:按租户路由的 `multi-tenant.ts`(云产品差异化能力)及其 schema、
25+
`vector-poc.test.ts`。因此本包的 barrel **不再导出** `createMultiTenantRouter` /
26+
`MultiTenantConfig` / `MultiTenantRouter`,也不导出多租户 schema——它们从来不是这个 driver 的
27+
一部分,只是曾经同包而已。
28+
29+
**目录重组**:五个 `IDataDriver` 实现(`driver-memory` / `driver-mongodb` / `driver-sql` /
30+
`driver-sqlite-wasm` + 迁入的 `driver-turso`)现在都住在 `packages/drivers/`
31+
`knowledge-*``embedder-*` 留在 `packages/plugins/`。四个存量包**内容零改动**,只有
32+
`repository.directory` 随目录更新——包名、入口、导出面、行为全部不变,消费者无需改动任何 import。
33+
34+
这也把 turso 交给了本仓的仓库级守卫:`check:driver-conformance` 从磁盘发现 driver 包,
35+
迁入即入矩阵(5 drivers × 5 case-sets)。它的 temporal 两格是真绿(local 与 remote 双面套件),
36+
filter 组合语义与两个分页 case-set 记为 measured DEBT——remote 传输自带一套 `buildWhereSQL`
37+
`LIMIT`/`OFFSET` 拼装,是独立实现,"继承所以没问题"正是这些共享套件存在来证伪的假设。
38+
补齐工作跟踪在 #5590
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): 元数据保存的 422 保留 union 分支处方,Studio 重新拿得到字段名 (#5364)
6+
7+
`saveMetaItem` 的 spec-conformance 检查在自己的注释里承诺 "structured Zod issues
8+
so the Studio form can highlight the offending field"。顶层 `z.union` 让这句承诺
9+
彻底落空:zod 把一个失败 union 的**所有**分支折叠成**一条**顶层 issue,`path`
10+
空串、`message` 是字面量 `"Invalid input"`,而旧代码的 `parsed.error.issues.map(…)`
11+
映射的正是这一条。
12+
13+
代价不是"文案不够好",而是**字段定位本身消失了**`ViewMetadataSchema` 顶层就是一个
14+
union(`view.zod.ts``z.preprocess(…, z.union([…]))`),所以**每一次** view 保存
15+
失败都退化成:
16+
17+
```json
18+
[{ "path": "", "message": "Invalid input", "code": "invalid_union" }]
19+
```
20+
21+
一个字段名都没有到达作者,Studio 表单没有任何东西可以高亮;422 的摘要行也只是
22+
`... failed spec validation: <root>: Invalid input`。被丢掉的分支里躺着的恰恰是
23+
#4001 那批策展处方(点名真实键名的 unrecognized_keys)和带绝对路径、带合法枚举的
24+
逐槽位判决。
25+
26+
现在这些分支被展开进 `issues[]`:union 自己那条**保留不动**(展开是严格叠加的,
27+
今天读 `issues[0]` 的消费者不会少读到任何东西),后面跟上真正解释这次拒绝的分支,
28+
路径按绝对路径拼好——分支 issue 的 `path`**相对于 union** 的,这是 #5014 付过
29+
学费的坑。422 的 `message` 摘要行随之变得可读。
30+
31+
分支选择策略与已落地的两处**逐条一致**:丢弃只报根部 kind 不匹配的分支;报得最少
32+
的分支胜出;`unrecognized_keys` 破平局;声明顺序决定其余;并列的全部输出(上限 3);
33+
嵌套 union 递归展开(上限 3 层)。这是同一机制的**第三份**拷贝——`packages/spec`
34+
`formatZodError`(#4971)只导出字符串渲染器,`packages/rest`
35+
`zodIssuesToFields`(#5014)产出 ADR-0114 的 `{field, code}` 目录条目,而本处的信封
36+
`{path, message, code}``code` 透传 zod 原码——形态不同,**判决必须相同**,
37+
否则同一个错误会因为作者是从终端发布、还是 POST 数据 API、还是在 Studio 里保存,
38+
拿到三套说法。
39+
40+
行为边界:合法的元数据照常保存,非法的元数据照常被 422 拒绝且不落库;变的只是
41+
`issues[]` 从"一条无字段的 `Invalid input`"变成"那一条 + 真正解释它的分支"。

.claude/agents/os-dev.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,23 @@ backslash-u forms like `\u0000` / `\u0001` — never as raw bytes, in **any**
270270
file (source, markdown, fixtures) and in any prompt or tool payload you
271271
compose: describe the escape, do not paste the byte. Editing tools
272272
materialize escapes into real control bytes precisely when you are writing
273-
*about* them — this repo has paid four times: #4763 (raw NUL in a dispatch
274-
prompt), #4890 (a raw NUL landed in `SKILL.md` **while writing the
275-
no-raw-NUL rule**, outside every gate's scan surface), and PR #5140's two
276-
bytes — a NUL plus, 14 bytes away, a `0x01` that `check:nul-bytes` does not
277-
scan for (#5157). One raw control byte makes grep treat the whole file as
278-
binary: zero matches, no signal, and the rule you just wrote becomes
279-
invisible to every agent that greps for it. Run
273+
*about* them, and this repo has paid for it repeatedly — including #4763
274+
(raw NUL in a dispatch prompt), #4890 (a raw NUL landed in `SKILL.md`
275+
**while writing the no-raw-NUL rule**, outside every gate's scan surface),
276+
and PR #5140's two bytes: a NUL plus, 14 bytes away, a `0x01` that the
277+
then-NUL-only scan walked straight past — the gap #5157 closed by widening
278+
the scan surface beyond NUL. The harms are argued in the gate script's
279+
header (`scripts/check-nul-bytes.mjs`) — cite it, don't re-derive it.
280+
Measured, only a raw **NUL** makes grep and ripgrep treat the whole file as
281+
binary and report zero matches with no signal, so the rule you just wrote
282+
becomes invisible to every agent that greps for it. Every other scanned byte
283+
(`0x01`, `0x7f`, …) keeps matching line by line, and is rejected for the
284+
three harms that land on the whole set: it **renders as nothing**, so the
285+
code lies to every reader; it is unfindable in **both** spellings, since the
286+
file holds a byte and not the escape text you would search for; and the
287+
accident source **does not pick byte values**. "Mine is not a NUL and grep
288+
still finds my file" is therefore never a reason to read a gate failure or a
289+
self-scan hit as a false positive. Run
280290
`node scripts/check-nul-bytes.mjs` before pushing, and when your change so
281291
much as *mentions* control characters, self-scan beyond the gate
282292
(`grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' <files>`) — the gate's blind

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ file the fix touches, you have not triaged it yet, and it is not labelable.
392392
| 标签 | 包家族 |
393393
|:--|:--|
394394
| `domain:engine-core` | `packages/objectql``packages/metadata*``packages/platform-objects``packages/core``packages/formula`(CEL / `matches-filter` / RLS 谓词求值)、`plugin-pinyin-search`(`__search` 伴生列由 SchemaRegistry 声明、engine 把它 OR 进 `$search`,落点在编译/查询核心而非任何 driver;全局写钩子同 #4775 锚定) |
395-
| `domain:drivers` | `packages/plugins/driver-*`(`driver-memory` / `driver-mongodb` / `driver-sql` / `driver-sqlite-wasm`) |
395+
| `domain:drivers` | `packages/drivers/driver-*`(`driver-memory` / `driver-mongodb` / `driver-sql` / `driver-sqlite-wasm`) |
396396
| `domain:services` | `packages/services/*``packages/connectors/*``packages/triggers/*`(flow 触发器)、`packages/plugins/plugin-approvals``plugin-webhooks``plugin-email``plugin-reports``embedder-openai``knowledge-memory``knowledge-ragflow` |
397397
| `domain:identity` | `packages/plugins/plugin-auth``plugin-security``plugin-sharing``plugin-audit` |
398398
| `domain:devx` | `packages/lint``packages/sdui-parser`(仅 lint 消费)、`packages/vscode-objectstack``skills/**``content/docs/**``apps/docs``scripts/`(门禁类) |

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ jobs:
959959
- name: Verify capability packages ship a runtime entry (no dts-only / half-built)
960960
run: |
961961
fail=0
962-
for d in packages/triggers/* packages/services/* packages/plugins/*; do
962+
for d in packages/triggers/* packages/services/* packages/drivers/* packages/plugins/*; do
963963
[ -f "$d/package.json" ] || continue
964964
has_build=$(node -p "Boolean((require('./$d/package.json').scripts||{}).build)" 2>/dev/null || echo false)
965965
[ "$has_build" = "true" ] || continue

.github/workflows/lint.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,8 +618,9 @@ jobs:
618618
# The per-package `typecheck` scripts the coverage gate above requires
619619
# (#4311). tsc resolves workspace imports through each dependency's built
620620
# dist/*.d.ts, so the task carries `dependsOn: ^build` in turbo.json —
621-
# which also builds the handful of nested packages (packages/plugins/*,
622-
# packages/services/*, …) the build step's direct-children glob misses
621+
# which also builds the handful of nested packages (packages/drivers/*,
622+
# packages/plugins/*, packages/services/*, …) the build step's
623+
# direct-children glob misses
623624
# when no example depends on them. Three filters because the bare
624625
# `./packages/*` glob only matches direct children (see the build step's
625626
# comment): the nested group dirs and apps/ (docs) need their own globs.

ARCHITECTURE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,13 @@ ObjectStack is organized as a **monorepo** with distinct package layers:
242242
**Dependencies**: `@objectstack/client`, `@objectstack/core`, `@objectstack/spec`
243243
**Peer Dependencies**: `react`
244244

245-
### Plugin Packages
245+
### Driver & Plugin Packages
246246

247-
Located in `packages/plugins/*`:
247+
Drivers (`IDataDriver` implementations) live in `packages/drivers/*`; every other
248+
official plugin lives in `packages/plugins/*`.
248249

249250
#### `@objectstack/driver-memory`
250-
**Location**: `packages/plugins/driver-memory/`
251+
**Location**: `packages/drivers/driver-memory/`
251252
**Role**: In-Memory Driver (Reference Implementation)
252253

253254
- Complete ObjectQL driver implementation

0 commit comments

Comments
 (0)