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

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 mismatch — code === 'ERR_DLOPEN_FAILED' / …NODE_MODULE_VERSION… (stale prebuilt binary after a Node upgrade)
  • not built — Could 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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface 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 的 columns 加 managed_by,:99/:109/:119);sys-position.object.ts(同上)。sys-capability.object.ts 已有 managed_by 列,不动。
后端 无。
前端(badge) 分两级:① 省事版(纯 framework)——把 sys_position.managed_by 由 Field.text 改 Field.select 带 options(platform/package/system/user),渲染器对 select 通常出 chip;② 完整版(跨仓 objectui)——要"平台锁/包提供/自建"三态彩色 source badge,需在 objectui 加一个 cell renderer 或 field widget(本仓 field.zod.ts 的 widget 契约声明处,值由 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:55 的 GOVERNED_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.ts、rls-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

This branch was successfully deployed

1 active deployment
Preview — d0667fd6 Deployed Jul 14, 2026 by vercel[bot]
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants