Skip to content

fix(driver-sql): don't dump a scary ERR_DLOPEN stack for the native→wasm probe - #2892

Merged
os-zhuang merged 2 commits into
mainfrom
claude/dev-server-startup-failure-m8om34
Jul 14, 2026
Merged

fix(driver-sql): don't dump a scary ERR_DLOPEN stack for the native→wasm probe#2892
os-zhuang merged 2 commits into
mainfrom
claude/dev-server-startup-failure-m8om34

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Context

Follow-up to #2890. That PR fixed the fatal pnpm dev crash on a better-sqlite3 ABI mismatch (the showcase fixture now steps down to wasm instead of bricking app-plugin boot). But the dev console still looked broken: the native-load failure was logged with the full multi-line ERR_DLOPEN_FAILED / NODE_MODULE_VERSION stack trace — twice — so a handled, non-fatal step-down reads as a fatal crash.

Failed to set PRAGMA auto_vacuum=INCREMENTAL Error: The module '…better_sqlite3.node'
was compiled against a different Node.js version using NODE_MODULE_VERSION 127 …
    at Object..node (node:internal/modules/cjs/loader:1996:18)
    at new Database (…/better-sqlite3/lib/database.js:48:64)
    … (10+ stack frames) …
  ⚠ native better-sqlite3 unavailable … dev using wasm SQLite

Why it surfaces there

SqlDriver.connect() runs a best-effort PRAGMA auto_vacuum = INCREMENTAL. That PRAGMA is the first query to force better-sqlite3's lazily-loaded .node addon to load, so a native ABI/build failure lands in its catch first — where it was logged with the full error object (stack and all) — before resolveSqliteDriver's probe catches the follow-on failure and prints its clean one-line step-down notice.

Fix

In that one catch, detect the native-load-failure family and log a concise, actionable one-liner instead of the stack. Covers both real-world variants the step-down already handles:

  • ABI mismatchcode === 'ERR_DLOPEN_FAILED' / …NODE_MODULE_VERSION… (stale prebuilt binary after a Node upgrade)
  • not builtCould not locate the bindings file / MODULE_NOT_FOUND (native addon never compiled)

Any other PRAGMA failure keeps the full warning (with stack) unchanged. Purely a log-noise change — control flow and the fallback behavior are untouched.

Before → After (native disabled, reproducing the ABI mismatch):

  (10+ line ERR_DLOPEN stack, ×2)
      ↓
native better-sqlite3 unavailable (ABI mismatch or not built) — will step down to wasm SQLite; run `pnpm rebuild better-sqlite3` for native speed
  ⚠ native better-sqlite3 unavailable (ABI mismatch or not built) — dev using wasm SQLite (real SQL, slower).

Verification

  • @objectstack/driver-sql test suite: 275/275 pass (31 files).
  • End-to-end pnpm dev with the native binary disabled: 0 stack-trace lines remain, the concise one-liner appears, and the server still boots (API listening, no Plugin startup failed).
  • driver-sql builds cleanly (incl. .d.ts).

Note: the definitive fix on an affected machine is still pnpm rebuild better-sqlite3 (restores the native driver at full speed) — this PR just makes the fallback's console output honest about being non-fatal.

🤖 Generated with Claude Code


Generated by Claude Code

…asm probe

When native better-sqlite3 has an ABI mismatch (`NODE_MODULE_VERSION` after a
Node upgrade), `resolveSqliteDriver`'s probe deliberately triggers the lazy
`.node` load to detect it and steps down to wasm SQLite with a clean one-line
notice (#2229). But `SqlDriver.connect()`'s best-effort `PRAGMA auto_vacuum`
is the first query to force that load, so the failure surfaced there first and
was logged with the full multi-line `ERR_DLOPEN_FAILED` stack — twice — making
a handled, non-fatal step-down look like a fatal crash to anyone reading the
dev console.

Detect the native-addon load failure (`code === 'ERR_DLOPEN_FAILED'` or a
`NODE_MODULE_VERSION` message) in that catch and log a concise, actionable
one-liner instead of the stack, pointing at `pnpm rebuild better-sqlite3`. The
following step-down notice still explains the outcome. Any OTHER PRAGMA failure
keeps the full warning (with stack) unchanged. Purely a log-noise fix — control
flow and the fallback behavior are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUWGgTT5s7XgBb2eLtzhfU
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 14, 2026 3:57am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql.

7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-sql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…g-noise fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUWGgTT5s7XgBb2eLtzhfU
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Jul 14, 2026
@os-zhuang

Copy link
Copy Markdown
Contributor Author

实现细节补充

原 issue 是 gap + 期望;这里给可直接落地的 P0 项的文件级实现(含前后端边界、测试)。B/C 段为什么现在不写文件级 spec,见文末。

前后端边界前提:渲染器 objectui 不在本仓。凡是 record:related_list / listView columns / action 都是元数据(本仓 framework),渲染器已支持、无需改前端;只有需要新单元格渲染器 / 新字段 widget 时才跨仓到 objectui


A1 · 用户页补 BU 成员 + 直接授权集两个 tab

维度 内容
涉及包 @objectstack/platform-objects(仅一个文件)
文件 packages/platform-objects/src/pages/sys-user.page.ts(在 slots.tabs.properties.items 追加两项)
后端 。目标对象 sys_business_unit_member / sys_user_permission_set 已存在;写入走通用 CRUD。sys_user_permission_set 已在 delegated-admin-gate.ts:55 GOVERNED_OBJECTS 内,委派写治理自动生效。
前端 record:related_list + add.picker 渲染器已支持,模板见 sys-position.page.ts:56-104

两个 tab 的 config(照 sys-position.page.ts 形状):

  • Business Units tab → objectName: 'sys_business_unit_member', relationshipField: 'user_id', columns: ['business_unit_id','function_in_business_unit','is_primary','created_at'], add.picker.object: 'sys_business_unit'(labelField:'name'), add.linkField: 'business_unit_id'
  • Permission Sets tab → objectName: 'sys_user_permission_set', relationshipField: 'user_id', columns: ['permission_set_id','organization_id','created_at'], add.picker.object: 'sys_permission_set'(labelField:'label'), add.linkField: 'permission_set_id'

测试:

  • os validate(元数据门)过。
  • 浏览器 dogfood:用户记录页出现两 tab、Add 能建行。
  • 回归点:加 BU 成员后,sys_user.primary_business_unit_id(plugin-sharing 维护的去规范化投影)是否按 is_primary 刷新——若不刷新是既有 bug,单列。

A2 · RBAC 列表加 managed_by 列 + source badge

维度 内容
涉及包 @objectstack/plugin-security(列,元数据);badge 视觉可能跨到 objectui
文件(列,纯元数据) packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts(3 个 listViews 的 columnsmanaged_by:99/:109/:119);sys-position.object.ts(同上)。sys-capability.object.ts 已有 managed_by 列,不动。
后端
前端(badge) 分两级:① 省事版(纯 framework)——把 sys_position.managed_byField.textField.select 带 options(platform/package/system/user),渲染器对 select 通常出 chip;② 完整版(跨仓 objectui)——要"平台锁/包提供/自建"三态彩色 source badge,需在 objectui 加一个 cell renderer 或 field widget(本仓 field.zod.tswidget 契约声明处,值由 objectui 实现)。建议先做 ①。

测试:元数据校验 + 浏览器目检列可见、chip 正确。


A3 · 内置行写护栏(后端,安全)

维度 内容
涉及包 @objectstack/plugin-security
文件 packages/plugins/plugin-security/src/security-plugin.ts——新增 assertSystemRowWriteGate(opCtx),挂在与 assertPackageManagedWriteGate 同一个数据写中间件 hook(:501 一带)。
逻辑 object ∈ {sys_position, sys_capability}:目标行 managed_by ∈ {system, platform, package}isSystem 上下文 → 拒绝 update/delete。fail-closed,连 modifyAllRecords 也拦(与现有 two-doors 一致)。payload 里伪造 managed_by 降级也拒。
落点取舍 不要直接把 sys_position/sys_capability 塞进 delegated-admin-gate.ts:55GOVERNED_OBJECTS——那个 gate 语义是"委派 scope 边界",不是"来源保护",混用会污染语义。用独立 gate。
前端 无(可选:受保护行的删除按钮用 action visible 谓词隐藏,但后端 fail-closed 才是硬保证)。

测试:新增 packages/plugins/plugin-security/src/system-row-write-gate.test.ts,照 cleanup-package-permissions.test.ts / delegated-admin-gate.test.ts 的 boot-kernel 模式:

  • 非系统上下文 delete platform_admin 岗位 → 拒;
  • 非系统上下文改自建岗位(managed_by=user/config)→ 放行;
  • isSystem 上下文 → 放行;
  • payload 伪造 managed_by:'system' → 拒。

B / C 段为什么现在不写文件级 spec(避免假精确)

  • B(posture 阶梯 / 拆租户隔离层 / PG RLS backstop) 改的是 packages/core/src/security/authz-context.tsrls-engine.ts)+ plugin-sharing,是高 blast-radius 的运行时授权路径。改法本身是设计决策(posture 枚举定义、短路顺序、第 0 层租户隔离的边界、backstop 的 GUC 契约)。应先出 ADR(承 ADR-0066/0090),ADR 定案后再拆实现 PR。现在列文件清单会把"没定的设计"伪装成"已定的任务"。
  • C(defineCapability 声明链) 要动 packages/spec(新 zod schema + stack config 字段)+ bootstrap + cleanup,是新的 author-facing 契约,同样走 ADR/spec 评审再落地。

落地顺序建议:A1 + A3 可即刻开 PR(各自独立、可独立验证);A2 先做 ① 省事版;B1(拆租户隔离层)作为 B 段地基,先出 ADR。

@os-zhuang
os-zhuang marked this pull request as ready for review July 14, 2026 05:10
@os-zhuang
os-zhuang merged commit 84650c5 into main Jul 14, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/dev-server-startup-failure-m8om34 branch July 14, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants