Repository navigation
Commit 01516dc
docs(data-modeling): virtual objects — rows from your server, shown in any view (plugin-driver recipe) (#27349)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiVy7F2atKWpo5rp3YPrqddh)_
Fixes #27278
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么:** 新增文档页「虚拟对象」:三分法(计算型 / 不可落地 / 实时 ⇒ 虚拟;慢变参考数据 ⇒ 真对象 + 定时拉取)、插件
driver 完整配方(driver 骨架、插件、数据源、外部对象、`current_user` 锁公式、`beforeUpdate`
守卫、甘特视图、权限集)、driver 实际收到的调用、共享模型规则、driver 形状 pin 测试模板、五个业务例;从
external-datasources、drivers 与模块首页链入。
- **为什么改:** #27130 这类客户要「行来自自家服务器、任何视图可看可改」,平台零协议改动已能做到,但没有一页可照抄的配方;AI
作者也照这页写。
- **风险与回滚:** 纯文档(外加文档审计范围清单自动生成的一行),revert 即回滚;页内整套栈与 driver 骨架已在真实
kernel 中启动跑通(见 Tests)。
- **先看:** `content/docs/data-modeling/virtual-objects.mdx`
的「Credentials」段(实测:插件 driver 的数据源上 `credentialsRef` 不被解析)与「Choose the
sharing model on purpose」提示框;其次第一个 driver 骨架代码块。
- **两分钟验证:** `pnpm --filter @objectstack/spec build && pnpm --filter
@objectstack/spec check:skill-examples`,预期 `✅ 272 prose examples
type-check`,页内两个 `os:check` 代码块在其中(变异后报 `virtual-objects.mdx:140` 与
`:288`)。
- **需要你做的:** 定夺插件 driver 的凭据通道(Acceptance notes 第 1
条);页面目前只写实测事实,并按插件约定让宿主经插件选项传入。
## What changes
- **New page `content/docs/data-modeling/virtual-objects.mdx`**
("Virtual objects — rows from your server, shown in any view"):
- the three-way rule as a decision table (computed / must-not-land /
real-time ⇒ virtual; slow-changing reference data ⇒ a real object fed by
a scheduled pull), plus the pointer to federation when the remote is a
reachable SQL database;
- the recipe, worked as a port berth plan: the driver skeleton
(translate the whole `where` or refuse by name, forward only
remote-owned columns on `update`, `supports.transactionsUnsupported`),
the plugin that publishes `driver.NAME` in `init`, and one `defineStack`
block with the datasource (`driver` = the service suffix, `config: {}`,
`checkOnBoot: false`), the external object (`sharingModel:
'public_read_write'`, the per-caller `locked` formula over
`current_user`), the `beforeUpdate` guard with declared refusals, the
gantt list view and a permission set;
- the "a driver cannot opt out of correctness" rules, with #27277's
engine floor named as planned, not existing;
- the sharing-model callout (default model refuses every member edit;
link #27332, no fix promised);
- what the driver receives, call by call, as measured (object name not
`remoteName`, `object` inside the query, the extra `id` / `updated_at` /
`updated_by` keys, by-id reads before a write);
- the driver shape-pin test template;
- five business examples: port berth scheduling, SAP sales orders, HR
directory, equipment live status, and the counter-example (codes and
registries as a real object with a `mapping` + `job` pull).
- **Links in:** a callout sentence and a See-also row on
`external-datasources.mdx`, a paragraph after "Writing a driver" on
`drivers.mdx`, a card on `data-modeling/index.mdx`, the nav entry in
`data-modeling/meta.json`.
- **`scripts/docs-audit/handwritten-docs.json`:** the one-line
regeneration `check-audit-scope.mjs --write` produces for a new page.
## Tests
Docs only; nothing published changes. Every code block on the page was
run before it was written down:
- **The whole recipe boots.** A throwaway copy of the reference test's
boot harness (`packages/runtime`, same plugin set as
`plugin-driver-external-object.spike.test.ts`) with the page's driver
skeleton, plugin and `defineStack` block (identical but for comments)
behind an in-memory transport: 6/6 passed under the verify lock
(`VERDICT command-exit 0`). Measured: list read with the view's filter,
sort and `top=50` reaches the remote as `filter [archived eq false]`,
`sort start_date asc`, `limit 50`, then a `count` with the same filter;
the formula reads `locked: false` for the planner and `true` for the
administrator; a PATCH on a locked slot answers `409 RESOURCE_CONFLICT`
/ `declaredCode: PLAN_LOCKED`; the administrator's PATCH answers `403
PERMISSION_DENIED` / `declaredCode: NOT_PLANNER` with no remote write;
the planner's PATCH makes three by-id reads and one `patch` carrying
exactly `start_date`, `end_date`, `plan_note`; an outsider's list read
is `403`; a filter the driver refuses (`>=`) answers `500
INTERNAL_ERROR`. The probe files were deleted; the tree carries no new
test.
- **The shape-pin template passes against the skeleton:** 5/5 (`vitest
run`, identical text to the page's block except the import path).
- **The reference test still holds on this base:**
`plugin-driver-external-object.spike.test.ts` 12/12 (`VERDICT
command-exit 0`).
- **The `os:check` blocks are really compiled:** with
`transactionsUnsupported: 'yes'` and `sharingModel: 'public_rw'`
injected, `check:skill-examples` exits 1 naming
`virtual-objects.mdx:140:17` and `:288:7`; restored by `git checkout
HEAD`, blob hash equal to HEAD.
## Acceptance notes
1. **`credentialsRef` is not resolved for a plugin driver** (measured,
seam level). `DatasourceConnectionService.attemptConnect` returns
`already-registered` for a driver a plugin registered before it reaches
credential resolution. Probe on `6d7634a9`: pre-registered driver +
`credentialsRef` ⇒ `already-registered` with no secret store and with a
resolver (resolver calls 0); not pre-registered ⇒ `failed-credentials` /
`connected` (resolver calls 1). So the fail-closed sentence in
external-datasources §4 does not hold for a plugin driver, and the
epic's standing rule "credentials only through `credentialsRef`" has no
channel on Tier A today. The page states the fact and points to the
plugin convention (the host passes options to the plugin it composes);
it does not claim a platform channel. Not filed: the reach is a seam
probe, not a public door.
2. **"DataSync" in the card is spelled as what exists.**
`DataSyncConfig` is retired (never executed); a sync is defined on its
target as a `mapping` with `connectorSource` driven by a `job` `pull`.
The counter-example links `/docs/automation/jobs#pulling-a-mapping` and
says there is no separate data-sync type.
3. **Mounting through `defineStack({ plugins })` is not measured under
`objectstack serve`.** The boot probe composes with `kernel.use`, as the
reference test does; the `plugins` sentence follows the documented
plugin convention (`plugins/development` step 6), and the "register in
`init`" rule rests on reading `ObjectQLPlugin.start` (adopts `driver.*`
services) and `AppPlugin.start` (connects declared datasources).
4. **Owner-column route** (the second way out of the sharing rule) is
named and marked as not covered by the reference test.
5. **#27130 is not answered here**; per the card the triage seat replies
after this lands.
6. **The card's #27277 item "Driver shape pin test template in the guide
(P1-2)"** is carried by the template section; #27277's helper, row cap
and refusal envelopes are described only as planned.
## Verification
Final head `beadd67a`. `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives 53 families for this diff;
all 53 run, all exit 0; `--ran` reconciles `53 derived, 53 run, 0
NOT-MEASURED, 0 UNRUN`.
<details><summary>Docs gates (selected verdict lines)</summary>
- `check:doc-anchors`: 803 internal fragment links across 476 source
files all resolve.
- `check:docs-audit-scope`: green after the regeneration.
- `node scripts/docs-audit/check-affected-docs.mjs`: exit 0;
`affected-docs.mjs origin/main`: 0 docs (no `packages/**` change).
- `check-section-landing-index`: 9 section index blocks enumerate their
`meta.json` pages (red before the index card was added, green after).
- `check:skill-examples`: 272 prose examples type-check (needs
`@objectstack/client-react` built with declarations first, otherwise it
refuses with exit 3).
- `check:doc-formula-expressions`, `check:doc-security-posture`,
`check:doc-authoring`, `check:doc-frontmatter`, `check:docs-single-h1`,
`check:nul-bytes`: exit 0.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d34533c commit 01516dc
6 files changed
Lines changed: 554 additions & 1 deletion
File tree
- content/docs/data-modeling
- scripts/docs-audit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
305 | 305 | | |
306 | 306 | | |
307 | 307 | | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
308 | 313 | | |
309 | 314 | | |
310 | 315 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
| |||
305 | 307 | | |
306 | 308 | | |
307 | 309 | | |
| 310 | + | |
| 311 | + | |
308 | 312 | | |
309 | 313 | | |
310 | 314 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| 63 | + | |
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
0 commit comments