Skip to content

test(driver-sql): pin the SORT axis' unprovisioned-injected-anchor premise (#10744) - #11165

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10744-sort-axis-unprovisioned-anchor-pin
Aug 22, 2026
Merged

test(driver-sql): pin the SORT axis' unprovisioned-injected-anchor premise (#10744)#11165
os-zhuang merged 1 commit into
mainfrom
claude/issue-10744-sort-axis-unprovisioned-anchor-pin

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #10744

Test-only. One new file, packages/drivers/driver-sql/src/sql-driver-external-unprovisioned-sort-anchor.test.ts. No runtime code is touched, and nothing here changes behaviour.

What is pinned, and why it needs pinning

sort-field-unprovisioned (@objectstack/lint, shipped by #10474) is an author-time WARNING about a list view ordering by a registry-injected anchor on an ADR-0015 external object. Its entire warrant is a runtime fact: neither SORT door refuses that ORDER BY, so the authoring gate is the only door there is. Nothing in the repo asserted that fact, so if a door ever started refusing it, the warning would silently become wrong — describing a silent degradation that no longer happens, on a path that is now a loud 400, at the wrong severity — with nothing turning red.

Both doors narrow on formula alone, which is why an injected datetime/lookup anchor sails through:

Neither file is edited here; both were read only. packages/drivers/driver-sql sits below both in the dependency graph and cannot import either door, so this file pins the half that is observable from the driver — the ORDER BY reaches the driver and the driver answers success with the sort dropped — plus the type half, asserted from the spec's own injected definitions (no anchor is a formula), which is the property both doors narrow on.

Measured on this file's own fixture

A real SqlDriver over better-sqlite3 in schemaMode: 'external', against a remote customers table carrying exactly [id, name, email, region, lifetime_value] and none of the seven injected anchors:

orderBy name       asc -> [c1,c2,c3]   desc -> [c3,c2,c1]   (a real column: reverses)
orderBy created_at asc -> [c1,c2,c3]   desc -> [c1,c2,c3]   asc === desc, 3 rows, no error
orderBy owner_id   asc -> [c1,c2,c3]   desc -> [c1,c2,c3]   asc === desc, 3 rows, no error

…and the same for the other five anchors (organization_id, updated_at, created_by, updated_by, owning_business_unit_id), which the test derives from unprovisionedInjectedColumns rather than typing out, so a future ADR that adds or removes an anchor is covered automatically.

One case records the mechanism from the emitted SQL, because "the sort was never issued" and "the sort was issued and dropped" are different facts:

select * from `customers` order by `created_at` desc   -- sqlite: no such column
select * from `customers`                              -- the #3821 ladder's retry

So the ORDER BY does reach the remote database, is rejected there, and is dropped by the #3821 recovery ladder, which returns the rows unordered under a success. (knex quotes identifiers with backticks on this stack, so SQLite raises rather than degrading the name to a string literal the way a double-quoted one would — the #5348 shape. Both routes end at the same answer; this is the measured one.)

Why the baseline leg is load-bearing

asc === desc on its own is satisfied by a driver that stopped sorting entirely — a far larger defect that would leave every anchor case green. The reversing control on a real remote column is what makes the anchor result a dropped sort rather than a coincidence. The card and triage both said the anchor legs must not ship without it, and the file's header says so too, with a ⛔ Do not delete the control to "simplify" this file beside the case.

That is not an assertion about the control — it was measured. See the second reverse-verification leg below: with the control's column made unresolvable (simulating "no sort is ever applied"), only the control goes red and all seven anchor legs stay green.

What a future red here means — the reason for the long header

A premise pin fails in an unusual direction, and the file header states it so a future reader does not misread it:

  1. Someone broke something — the driver stopped sorting, or the fixture drifted. The control is what separates this reading from the rest.
  2. Someone fixed the runtime — a door was widened to refuse an ORDER BY over an unprovisioned anchor, or the fix(sharing): 共享规则新建页 — 自定义 widget 未国际化,且「接收方」永远无可选项 #3821 ladder was narrowed into a hard error. That is a legitimate improvement, and the correct response is to retire or re-level sort-field-unprovisioned and delete the cases its warrant needed — not to restore the behaviour this file documents.

A premise pin whose red is misread as a regression gets "fixed" by putting back the very defect it records. Same posture, for the same reason, as packages/objectql/src/engine-external-tenant-scope.test.ts (#7738), the precedent this card names.

Fixture notes

  • The object is declared the way examples/app-showcase/src/data/objects/external/customer.object.ts declares it, copied rather than imported: a test that reads outside its own package is invisible to turbo's affected set and to the test task's input hashing. It is a fixture of that shape, not a mirror of that file.
  • The served field set is built the way applySystemFields builds it — the author's fields plus the spec's own injectedSystemColumnDefs. Engine.syncObjectSchema hands registerExternalObject the post-injection registry object, so this is what the driver really receives for a federated object.
  • The remote table is created with raw DDL rather than initObjects, so its column set is exactly the card's and carries nothing the platform would have injected. The FIXTURE case asserts that against the live columnInfo().
  • No engine double is declared (the real SqlDriver is used), so the assertEngineUpdateDispatch/assertEngineDeleteDispatch requirement does not arise. check:engine-double-contract ran green anyway.

Verification — union re-run at 7059f30d, clean tree

Reverse verification, both legs mutating the fixture only (the runtime is untouched), each under a restoring trap, each with the mutation confirmed on disk by grep counts before the run:

Mutation Predicted Observed
The remote table really carries created_at (i.e. "someone fixed the runtime") anchor leg red, control green 3 failed | 8 passedFIXTURE red, ORDER BY created_at red (desc now reverses), MECHANISM red (one statement, no ladder retry). Control and the other six anchors green.
The control's column made unresolvable (i.e. "no sort is applied at all") control red, anchors green 1 failed | 10 passed — only CONTROL a real remote column IS sorted red.

Gates, all at 7059f30d:

  • pnpm --filter @objectstack/driver-sql exec vitest runTest Files 108 passed | 5 skipped (113), Tests 1806 passed | 72 skipped (1878)
  • pnpm --filter @objectstack/driver-sql typecheck (tsc --noEmit) — clean
  • pnpm lint (eslint . --no-inline-config, whole repo) — clean
  • Path-derived set from node scripts/pm/dispatch-gates.mjs: check:driver-conformance, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check-ci-filter-parity.mjs, check-plugin-teardown-shape.mjs, docs-audit/check-affected-docs.mjs — all green
  • Convention-triggered (new test file): check:query-options-erasure, check:type-check-coverage, check:engine-double-contract, check:where-matcher — all green. The erasure ratchet was red on the first pass (test surface grew 240 → 242) and was fixed the way it prescribes — the two find calls now build a typed DriverQuery local instead of as any; the ratchet is back at 240 site(s) in 47 file(s) — at the ceiling, none new.
  • check:nul-bytes, check:cross-package-test-inputs — green

No changeset: this PR is test-only and publishes nothing, so it carries skip-changeset (this repo's mechanism for that; an empty-frontmatter changeset is refused here by check-empty-changeset.mjs).


Generated by Claude Code

…0744)

An ORDER BY over a registry-injected anchor on an ADR-0015 external object is
refused by neither runtime door and dropped by the #3821 ladder, so the rows
come back unordered under a success. That runtime fact is the entire warrant of
`sort-field-unprovisioned` (#10474), an author-time WARNING, and nothing
asserted it — so a future change at either door would leave the warning silently
wrong with nothing red.

The control leg (a real remote column, which reverses) is load-bearing: without
it a driver that stopped sorting entirely would keep every anchor leg green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfyXxZ2WPjcjhuXpiQQc3y
@github-actions

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.

@github-actions github-actions Bot added the tests label Aug 22, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 22, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 22, 2026 23:23
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32605126037 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.66s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 109 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 338044f Aug 22, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10744-sort-axis-unprovisioned-anchor-pin branch August 22, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the SORT axis' runtime premise is unpinned: neither door refuses an ORDER BY over an unprovisioned injected anchor, and nothing asserts it

2 participants