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 fatalpnpm 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

vercelBot commented Jul 14, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 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
@os-zhuang

Copy link
Copy Markdown
ContributorAuthor

实现细节补充

原 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:55GOVERNED_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 模式:

  • 非系统上下文 deleteplatform_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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude