fix(driver-sql): don't dump a scary ERR_DLOPEN stack for the native→wasm probe - #2892
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…g-noise fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUWGgTT5s7XgBb2eLtzhfU
实现细节补充原 issue 是 gap + 期望;这里给可直接落地的 P0 项的文件级实现(含前后端边界、测试)。B/C 段为什么现在不写文件级 spec,见文末。
A1 · 用户页补 BU 成员 + 直接授权集两个 tab
两个 tab 的 config(照 sys-position.page.ts 形状):
测试:
A2 · RBAC 列表加
|
| 维度 | 内容 |
|---|---|
| 涉及包 | @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 模式:
- 非系统上下文
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.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。
Context
Follow-up to #2890. That PR fixed the fatal
pnpm devcrash on abetter-sqlite3ABI 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-lineERR_DLOPEN_FAILED/NODE_MODULE_VERSIONstack trace — twice — so a handled, non-fatal step-down reads as a fatal crash.Why it surfaces there
SqlDriver.connect()runs a best-effortPRAGMA auto_vacuum = INCREMENTAL. That PRAGMA is the first query to forcebetter-sqlite3's lazily-loaded.nodeaddon to load, so a native ABI/build failure lands in itscatchfirst — where it was logged with the full error object (stack and all) — beforeresolveSqliteDriver'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:code === 'ERR_DLOPEN_FAILED'/…NODE_MODULE_VERSION…(stale prebuilt binary after a Node upgrade)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):
Verification
@objectstack/driver-sqltest suite: 275/275 pass (31 files).pnpm devwith the native binary disabled: 0 stack-trace lines remain, the concise one-liner appears, and the server still boots (API listening, noPlugin startup failed)..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