Skip to content

test(drivers): a keyset walk by id visits every organization member once, in the driver's order - #29659

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-3jyypm
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-3jyypm

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Fixes #29398

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:只加测试。五个 driver 包各加一份一致性钉子:按组织成员补全读的同一查询形状(organization_id + id $gt 游标、orderBy id、limit)做 keyset 分页,断言跨页拼起来恰好等于一次不分页的有序读,每个成员恰一次、不混入别的组织。
  • 为什么改:卡的第 1 项原本要在解析器里加 JavaScript 的「id 严格递增」校验,已实测证伪(数据库按 collation 排序,ICU en-US 下大小写混排的 id 顺序与码点不同,会让每一页都 503)。席位改判为:解析器不加校验,改由 driver 一致性测试钉住「游标与排序用同一比较」。
  • 风险与回滚:不改任何运行时代码,不发布(测试文件不进 dist),skip-changeset;回滚即 revert。
  • 先看:packages/drivers/driver-sql/src/sql-driver-member-keyset-walk-conformance.test.ts(按方言单元跑,含 live Postgres/MySQL),再看 driver-memory 的同名钉子。
  • 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-member-keyset-walk-conformance.test.ts,预期 sqlite 单元 4 个通过、未配置的 live 单元为具名跳过。
  • 需要你做的:无,席位落地。

What changes

Test files only, one per driver package. Each walks a sys_member-shaped table with the exact query shape of readOrganizationMembersToCompletion in @objectstack/core: where { organization_id } then { organization_id, id: { $gt: after } }, orderBy id asc, a limit, fields: ['id', 'user_id'], resuming after each full page's last id. For page sizes 1, 3, 4 (an exact multiple of the 16 rows) and 16, it asserts:

  • the pages joined are the same sequence as one unpaged orderBy id read, so the order asserted is the driver's own, never JavaScript's;
  • every member id appears once, and no row of the other organization appears;
  • each row carries its user_id through the projection;
  • the number of reads is the keyset's (floor(16 / pageSize) + 1), and a walk that does not end fails instead of hanging.

The fixture ids are mixed-case nanoid-alphabet values (AC Ab9 B-2 a-c aB3 ab b_1 0Zz Z0 z_9 _x -y Mm mN1 n Q-q), no two equal after case folding, plus three ids of another organization.

Package File Runs
driver-sql sql-driver-member-keyset-walk-conformance.test.ts every DIALECT_CELLS cell: sqlite always; live Postgres and MySQL when OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL are set (CI: Temporal Conformance (live PG + MySQL), which runs the whole package)
driver-memory memory-member-keyset-walk-conformance.test.ts always
driver-sqlite-wasm sqlite-wasm-member-keyset-walk-conformance.test.ts always
driver-turso turso-member-keyset-walk-conformance.test.ts always (local mode)
driver-mongodb mongodb-member-keyset-walk-conformance.test.ts opt-in, like the package's other live suites (OS_TEST_MONGODB_MEMORY_SERVER_ENABLED=1); a named skip otherwise

No changeset: no published file changes. Every driver package publishes only dist, README.md and CHANGELOG.md; after a build, member-keyset and org_walked have zero hits under the drivers' dist, and the control (ORDER in driver-sql/dist/index.js) hits. The PR carries skip-changeset.

Why

Item 2 of the card landed as #29545. Item 1 asked for a per-page "each id strictly greater than the last" check in the resolver. That check compares in JavaScript code-point order, but driver-sql orders and pages by the database collation, so on an ICU en-US PostgreSQL database it would refuse every correctly ordered page (measured in the report on the card). The seat ruled: no order check in resolve-authz-context.ts; instead, a driver conformance pin that the keyset walk returns every row once, in the driver's order, across page boundaries. That is this PR. resolve-authz-context.ts and every driver are untouched.

Tests

Per driver (head 1647e32)
  • driver-sql, sqlite cell: Tests 4 passed, 2 skipped (the unprovisioned live Postgres and MySQL cells, named).
  • driver-sql against a local PostgreSQL 16, two databases: C.UTF-8 and ICU en-US (CREATE DATABASE ... LOCALE_PROVIDER icu ICU_LOCALE 'en-US'). Both: Tests 8 passed, 1 skipped (MySQL). The two databases order the fixture differently, so both a code-point order and a non-code-point order were walked:
    • C.UTF-8 (same as JavaScript): -y 0Zz AC Ab9 B-2 Mm Q-q Z0 _x a-c aB3 ab b_1 mN1 n z_9
    • ICU en-US: _x -y 0Zz a-c ab aB3 Ab9 AC b_1 B-2 Mm mN1 n Q-q z_9 Z0
  • driver-memory: Tests 4 passed. driver-sqlite-wasm: Tests 4 passed. driver-turso: Tests 4 passed.
  • driver-mongodb: Tests 4 skipped. NOT MEASURED: no mongod here; the binary download host is refused by this container's proxy (403). No CI job sets OS_TEST_MONGODB_MEMORY_SERVER_ENABLED, so CI does not run it either, as for the package's existing pagination suite.
  • MySQL: NOT MEASURED locally (no server here); CI's live job runs it after merge.
  • pnpm typecheck for the five driver packages: exit 0 (head 3784be1; the later commit only merges main). The four src test files are in their package's tsc file list; the mongodb one is compiled by its check:test-typecheck step (OK).
The pin can fail (one-time mutation, no file kept)

A scratch copy of the driver-sql pin wrapped find so that every paged read returned its rows reversed (a driver whose pages are not in its own order). Run on sqlite and on the ICU en-US database, then deleted (git status clean afterwards):

  • sqlite: Tests 3 failed, 1 passed. Pages of 3, 4 and 16 are red (expected [ 'AC', '0Zz', '-y', 'Ab9', …(40) ] to deeply equal [ '-y', '0Zz', 'AC', 'Ab9', …(12) ]); pages of 1 stay green, since a one-row page has no order to lose.
  • ICU en-US Postgres: Tests 6 failed, 2 passed, the same split in both cells.

A second scratch copy dropped orderBy from every paged read instead. It stayed green on both, because driver-sql adds an id order to a limited read that names none. That is a reading about the driver, not a gap in the pin.

Verification

Gates (head 1647e32)
  • node scripts/pm/dispatch-gates.mjs --commands derived 49 commands. All 49 were run locally with exit codes captured before any pipe, and all exited 0. --ran reconciled them: 49 derived, 49 run, 0 NOT-MEASURED, 0 UNRUN.
  • The five artifact-roster families whose roster sits under a changed directory, run as well: check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:object-def-param-keys, check:tenant-chokepoint. All exited 0.
  • pnpm lint exited 0.
  • A first pass on an earlier head turned up one real red: check:query-options-erasure counted the test surface growing 236 to 246, because each pin cast its find options as any. The casts were removed (the options type-check as written), and the gate now reads 236, at the ceiling.
  • check-plugin-teardown-shape --self-test needed git fetch --unshallow (its fixture is pinned to an old commit).
  • Not run here: Temporal Conformance (live PG + MySQL) skips pull requests and the merge queue, so the live Postgres and MySQL cells of the driver-sql pin first run in main's push battery after merge. The local Postgres readings above stand in for the Postgres half.

Acceptance notes

  • The drivers' shared conformance cases live in @objectstack/spec/data (PAGINATION_CASES and its siblings). This change does not edit packages/spec, so each driver package carries its own copy of the fixture and the walk. Hoisting them into @objectstack/spec/data beside the pagination cases is a later choice, not a gap.
  • The pin follows the resolver's query shape by copy. If readOrganizationMembersToCompletion changes its shape, these files do not change with it.
  • MySQL's utf8mb4_0900_ai_ci is case-insensitive. Two ids equal after case folding would collide on a case-insensitive primary key at insert, so the fixture holds none. That is a different fact from the one pinned here, and it was not measured.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Nothing publishes: skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Card thread session: session_01JQwxbZ5Pwrph2FpoA9Ucqq, seat domain:server#4, claim 6590972532.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbZ5Pwrph2FpoA9Ucqq


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7dae3b3c0df1c3c91f2130b89c4fb3fd5ddb3549 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

席位核对

ACCEPT · domain:server#4 · head 1647e3243a69567de3f081e1fb436f799c94f59a · 2026-11-03T00:51Z · seat review of record at TIER_DEFAULT for the whole PR. No contract review is owed: test files only, Clause-②: no, no changeset (skip-changeset), no schema or governed path. Not governed: check-governed-merges --pr 29659 0 of 5 paths, +564/−0.

  1. Shape: draft, base main, Fixes #29398 (item 1; the card's union half landed as PR fix(core): the fellow-org peer list keeps every member the first membership read returned #29545). 5 new test files, one per driver package (driver-memory, driver-sql, driver-sqlite-wasm, driver-turso, driver-mongodb). Inside the claim surface (6590972532). No resolve-authz-context.ts, packages/spec or protocol.ts change.
  2. Premise measured: the seat ruled out the card's JS id-order check (measured on Postgres ICU collation, where mixed-case ids order differently from code points) and ordered a driver conformance pin instead. Each pin walks sys_member-shaped rows with the completion read's exact query (organization_id + id $gt, orderBy id, limit, fields id/user_id) over 16 mixed-case ids at page sizes 1/3/4/16. It asserts the joined pages equal one unpaged ordered read in the driver's own order, every member once, no foreign row. Green on sqlite, local PG16 (C.UTF-8 and ICU en-US), memory, wasm and turso; a reversed-page mutant reds on sqlite and ICU PG (dev report 6592084777).
  3. Gates on this head: check-runs pinned to 1647e324: 53 runs, none failed. Dev local: dispatch-gates 49 derived / 49 run, exit 0; the 5 driver roster families 0; pnpm lint 0. An earlier head reddened check:query-options-erasure through as any casts; they were removed.
  4. Accepted deviations: no changeset. Every driver package publishes only dist, README.md and CHANGELOG.md, and test files do not reach dist.
  5. Out of scope / not measured: the mongodb pin is opt-in like the package's other live suites (no mongod reachable, no CI job enables it). The MySQL cell runs in the live dialect job on main's push battery, after merge. Both are named skips, not silent passes.
  6. Landing: not governed, under 3000 lines, nothing owed above TIER_DEFAULT ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on core/security: the org-members completion read trusts the driver's orderBy and replaces the first read's rows, so a driver that drops the order, or a completion that sees fewer rows, can shorten org_user_ids silently #29398 (closed by Fixes), pm:dispatched removed.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 00:52
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 00:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit a2f77f7 Nov 3, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-3jyypm branch November 3, 2026 01:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

1 participant