Repository navigation
Commit f934a41
fix(spec): a user field over a text column passes the type-compat check; ADR-0015 names the odata vocabulary (#28011)
epic:#27261#1 · marchtian · session_017ihL2dhraZ7BwSanQPGEAb
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiH18zL8aJRKfkWPzKCnyMP2)_
Fixes #27917
Clause-②: yes (widening)
Metadata-protocol: none
**Fable review owed.** The review quota was out when this PR was opened.
It stays draft until the independent full review, the contract review
PASS on head and an authorized approval (Tier H: `docs/adr/**`).
## 维护者速读
- **改了什么:** 一是 ADR-0015 §4.6 的方言清单补上 `odata`,并指向
`DIALECT_ALIASES.odata`(代码在 #27880 已加)。二是 `user` 字段加入类型矩阵 `text` 行的
exact 集合:外部表里用文本列存平台用户 id 的 `user` 字段,启动校验不再报 `type_mismatch`
错误。整数行(`integer` / `bigint`)不加。
- **为什么:** 实测平台用户 id(`sys_user.id`)永远是字符串:better-auth 为每个用户生成 32
位字母数字串,引擎直写时 SQL 驱动生成 16 位 nanoid,既不是 UUID 也不是整数。文本列装得下,整数列装不下,所以只放宽
`text` 行。
- **风险与回滚:** 只放宽一个接受集合,之前通过的不会变成失败;回滚即 revert。
- **需要您做的:** 合并前批准(改了 ADR,Tier H)。
## What changes
- **`packages/spec/src/data/type-compat.ts`.** `user` joins the `text`
row's exact set (it was already exact under `uuid`). A comment on the
row records the measurement and why the integer rows stay closed while
`lookup` / `master_detail` are open there: a `user` field's target is
fixed to `sys_user`, so the column holds a platform user id, whose shape
is the platform's and not the remote system's.
- **`docs/adr/0015-external-datasource-federation.md`.** §4.6 lists
`odata` among the dialects, with one sentence naming the two non-SQL
vocabularies: `DIALECT_ALIASES.mongo` (BSON type names) and
`DIALECT_ALIASES.odata` (EDM primitive types, except `Edm.Duration` and
the geography / geometry families, left unmapped for review). The
`Amended by` line records it as a dialect-list-only amendment.
- **`packages/spec/src/data/type-compat.test.ts`.** One case pins the
decision both ways: `user` is exact over `varchar(255)`, `text`,
postgres `citext`, bigquery `STRING`, odata `Edm.String` (and still over
`uuid` / `Edm.Guid`); it is refused over `integer`, `bigint`, postgres
`int8`, `Edm.Int32`, `Edm.Int64`, `boolean` and `jsonb`, while `lookup`
stays exact over the same integer columns. A second case pins that the
draft still suggests `text` for a text column (the suggestion is read
from the column alone).
- **`.changeset/27917-type-compat-user-text.md`.** `@objectstack/spec`
patch, with both declaration lines.
## Why
### The measurement (`sys_user.id`)
- **better-auth 1.7.12, the version `@objectstack/plugin-auth` pins.**
Five `auth.api.signUpEmail` calls through a `createAdapterFactory`
adapter configured as `objectql-adapter.ts` configures it (no
`advanced.database.generateId`) wrote five `user` rows. Every id was 32
characters, all alphanumeric, not all-digit, not UUID-shaped (for
example `jaS4bSf4BGL50JWp7UaSlvZFEEAKHKiE`). Admin create and the user
import go through `auth.api.createUser`, the same id path.
- **The SQL driver.** A row written through the engine without an id
gets `nanoid(DEFAULT_ID_LENGTH)` with `DEFAULT_ID_LENGTH = 16`
(`packages/drivers/driver-sql/src/sql-driver.ts`), a string; a
caller-supplied id is stored verbatim.
- **Decision.** A text column carries every one of those, so `user` is
exact under `text`. No path produces an integer user id, so `user` stays
refused under `integer` / `bigint`. This follows the card's own
condition ("the integer rows only if a platform user id can be an
integer").
### What an author can declare
The `user` field's own schema is unchanged. The only reader of the
matrix that refuses is Gate 2 (`validateObject` in
`service-datasource`): a federated object with a `user` field over a
text column used to get a `type_mismatch` **error** (boot fails under
`onMismatch: 'fail'`) and now passes. Nothing that passed before fails
now. The draft generator never suggests `user` (suggestion is per column
and stays `text`), so imports are unchanged.
## Verification
- `pnpm --filter @objectstack/spec exec vitest run
src/data/type-compat.test.ts`: 29 passed.
- Reverse check from the committed fix: removing `'user'` from the
`text` row alone fails the new case at the `varchar(255)` assertion
(`expected false to be true`, line 190); restored with `git checkout
HEAD --`.
- `dispatch-gates --commands` derived 85 families on `ecf8b5c4`; all 85
run with exit codes recorded before any pipe. Four exited 3 on the first
pass (not measured: plugin-teardown self-test fixture,
doc-formula-expressions, dual-build-cjs-loads, lean-entry-closure) and
exit 0 after fetching the fixture commit and building `./packages/**`.
`--ran`: 85 derived, 85 run, 0 NOT-MEASURED (a derived zero).
- `pnpm --filter @objectstack/spec typecheck`: exit 0 (test-typecheck
ledger held). `vitest run src/data/` in spec: 149 files, 4588 passed.
## Contract review 6518256754 (FAIL on `ecf8b5c4`), fixed in `10deba35`;
delta 6518728501 PASS
- **Must-fix, ADR §4.6.** The added sentence called `odata` the one
vocabulary that is not SQL; `mongo` maps BSON type names too, as the
`SqlDialect` doc comment says. The sentence now names both, pointing at
`DIALECT_ALIASES.mongo` and `DIALECT_ALIASES.odata`.
- **Should-fix, same sentence.** It now says the odata arm leaves
`Edm.Duration` and the `Edm.Geography*` / `Edm.Geometry*` families
unmapped so a draft flags them for review.
- **Should-fix, `type-compat.ts` row comment.** "Never a UUID" held for
minted ids only. The comment now says a minted id is never a UUID or an
integer, and a caller-supplied id is stored verbatim as a string, which
is why the `uuid` row keeps `user`.
- **PR body.** The opening paragraph no longer names a model beyond the
card's own "Fable review owed" marker.
- The review's ③ also closed the memory and Mongo driver note below:
`driver-memory` mints an `objectName-timestamp-counter` string and
`driver-mongodb` a 16-character nanoid.
Delta review 6518728501 by the same reviewer: PASS on `10deba35`, every
finding closed.
Re-run in full on `10deba35`: `dispatch-gates --commands` derived the
same 85 families; all 85 run with exit codes recorded. Five spec gates
(api-surface, browser-reachable-entries, dual-source-exports,
entry-nameability, exported-any) exited 3 on the stale spec `dist/` and
exit 0 after `pnpm --filter @objectstack/spec build`. `--ran`: 85
derived, 85 run, 0 NOT-MEASURED (a derived zero).
## Acceptance notes
- **Integer rows.** Left closed by measurement, not by caution. If a
deployment ever configures better-auth with numeric ids, that is a new
id shape and its own card.
- **Memory and Mongo drivers** mint their own ids for engine-direct
writes, both strings (read by the contract review, not measured here).
Every user the platform creates goes through better-auth, which mints
the id before the driver sees the row.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(A matrix entry and its test only.)
- [x] 2. Every name I added uses the casing for its kind. (No new
names.)
- [x] 3. No formatter ran (`pnpm lint` is CI's).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: the type-compat matrix and its test,
no schema.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
repo's own Chinese reader section).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2dhraZ7BwSanQPGEAb
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7a37823 commit f934a41
4 files changed
Lines changed: 67 additions & 4 deletions
File tree
- .changeset
- docs/adr
- packages/spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
410 | 410 | | |
411 | 411 | | |
412 | 412 | | |
413 | | - | |
414 | | - | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
415 | 420 | | |
416 | 421 | | |
417 | 422 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
184 | 219 | | |
185 | 220 | | |
186 | 221 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
186 | 197 | | |
187 | 198 | | |
188 | 199 | | |
| |||
0 commit comments