Skip to content

objectstack dev --fresh 上 sys_audit_log 表不存在 —— 每次元数据写入都以 ERROR + 「Audit write failed」告终,审计轨迹静默丢失 #5226

Closed

Description

@os-zhuang

发现于 #5112(#5040 E8 收官验收)的真实 boot 探针。与 E8 无关的存量缺陷,按 Prime Directive #10 单独立项。

事实(showcase 真实 boot,objectstack dev --fresh -p <port> --seed-admin,47 plugins,含 Audit)

一次 PUT /api/v1/meta/api/<name>(Studio 元数据写)之后,日志里成对出现:

ERROR Insert operation failed {"object":"sys_audit_log","error":{"message":
  "insert into `sys_audit_log` (...) values (...) returning * - no such table: sys_audit_log", ...}}
WARN  Audit write failed {"object":"sys_metadata","action":"create",
  "err":"... - no such table: sys_audit_log"}
ERROR Insert operation failed {"object":"sys_audit_log", ...}
WARN  Audit write failed {"object":"sys_metadata_history","action":"create", ...}

写本身返回 200,业务数据落库正常;只有审计行没写进去,因为表在这个全新数据库里根本没被创建。

为什么这是 error 级而不是噪音

按 AGENTS.md 的「Degradation log levels」判据:降级之后系统从外面看完全正常(写入 200,数据在),而它声称会持久化的东西没有落地 —— 这是 durability / data-consistency 一类,不是 functional 一类。审计轨迹是合规面,丢失通常在很久以后、由无法把症状连回这一行的人发现。WARN Audit write failed 这一条按该规则应当是 error;更重要的是,表缺失本身是要修的。

待查的两个方向(未下结论)

  1. AuditPlugin 是否把 sys_audit_log 通过 manifest 注册进了 schema sync —— 若注册了,为什么 fresh DB 上没有建表;
  2. 还是它依赖某个在 dev 组合里没被装配的 service/对象包。

复现

pnpm dev:showcase -- --fresh -p 39720 --seed-admin --log-level debug
# 登录后做任意一次元数据写,例如 PUT /api/v1/meta/api/anything
grep "sys_audit_log" <dev log>

关联:#5112(发现于此)、AGENTS.md「Degradation log levels — warn vs error」、#4420(同一类:写入落空只留下没人读的日志)。

Activity

  1. self-assigned this
    on Aug 4, 2026
  2. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 2 轮(v17 C 组工作集,维护者指派)
    会话:session_01FTszibd6C8sUCCZnM4VcrL
    分支:claude/issue-5226-fresh-audit-table
    Worktree:objectstack-issue-5226
    域:标签为 domain:cli,但落点未定 —— 本条按借道认领(见下)
    文件面(声明,越界即停):packages/plugins/plugin-audit/**(尤其 src/objects/sys-audit-log.object.ts 与 audit-plugin.ts 的 manifest 注册面)、dev stack 装配面(packages/cli/** / showcase 示例的 stack 配置)、schema-sync 触发路径。⛔ 不碰 packages/spec/**,⛔ 不碰 .github/workflows/ci.yml(#4928 在飞)、⛔ 不碰 packages/spec/src/shared/error-map.*(#4971 在飞)。

    借道声明(domain lanes 协议)

    分诊读码结果:sys_audit_log 的对象声明在 packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts —— 按域表 packages/plugins/plugin-audit 属 domain:identity,不在本会话注册的域集 {spec-tooling, cli, devx} 内。但本单真实落点在调查清楚前无法确定(issue 自陈两个待查方向:plugin manifest 是否把对象注册进 schema sync / dev 组合是否根本没装配该 plugin),第二个方向落在 cli 面。

    按借道三条件逐条核对:

    1. 该域 PM 未认领本单 —— identity 车道(session_015W6nhsDrz6zWQc8je12a1t)10:30Z 交班评论明示「identity 队列:可派项清零」,10:46Z 恢复后范围未变;本单零评论、无 assignee、无分支(git ls-remote | grep 5226 空)。
    2. 文件面已声明(见上)。
    3. 全局在飞检查通过 —— 当前 pm:dispatched 全量(service-analytics 的第二个 SQL 编译器 filter-normalizer.buildNode 仍带着 #5297 的三条分叉:$not 非 NULL-safe、{$not:{}} 不加 WHERE、$or 的 {} 析取项被丢 #5325 service-analytics、[spec] api 补进 DEFAULT_METADATA_TYPE_REGISTRY 与 BUILTIN_METADATA_TYPE_SCHEMAS(#5206 第 1 步,拆单) #5271/[spec] FILTER_LOGIC_CASES 补空组合子的布尔单位元四条 —— 需与 driver-mongodb 的单位元归约同时落地 #5239/ADR-0120 17.x 收尾块:D5e isolated 安装期姿态硬门 + D6 文档/pin 测试扫荡 + 三姿态 conformance(阻塞于 #5030、#4986 的实现 PR) #5081/ui/ 五个交互配置文件(22 个 z.object 站点)没有任何承载键:实测「无授权门」,按 ADR-0049 定去留 #4988/未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 spec 面、api 不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206/[engine] driver-sql 编译 spec 未声明的「数组 where 方言」—— 与 Turso remote 的拒收分叉,需一次定调(接纳进 spec 或响亮弃用) #5158/loadMetaFromDb object branch reads record.packageId from a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636 engine 面、[P3] objectui ActionDef's [key: string]: any absorbs every retired and misspelled action key — removing a spec key produces no compile error #4075 objectui,加本会话 formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971/AREA_REQUIRED_PERMISSIONS_RETIRED 的处方在 #4722 之后过时:仍写着「the server does not walk areas」 #4749/gen:docs 把数组内的 passthrough 对象渲染成 Record&lt;string, any&gt;[],抹掉已声明键 —— #4001 战役每个 open 分类站点都会复发 #4912/filter job 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928)无一触碰 packages/plugins/plugin-audit/**。

    借道是单单例外,不改登记表归属;identity 车道 PM 若认为该单应归其调度,请在此留言,本会话即刻让行。

    本单由维护者在 v17 C 组工作集中点名指派(评估者首跑路径 + 合规面)。


    Generated by Claude Code

  3. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    复现结论:前提证伪,根因另在 —— 请重新分诊到 engine 车道

    会话 session_01FTszibd6C8sUCCZnM4VcrL,在 origin/main(1792384)上按 issue 给的命令跑了真实 boot。症状完全复现,但「表根本没建」这个前提不成立。

    1. 表建了,而且审计确实在写

    boot 日志:

    AuditPlugin: system tables provisioned — sys_audit_log→telemetry, sys_activity→telemetry,
                                             sys_comment→com.objectstack.driver.sql
    

    直接查文件:

    文件 有 sys_audit_log? 行数
    dev.db(主库) 否 —
    dev.telemetry.db 是 50

    lifecycle.class: 'audit' 被 ADR-0057 §3.6 路由到专用 telemetry 数据源(os dev 默认建兄弟文件)。这正是 #4887 记录过的「表在另一个 store 里」的误诊形状。

    所以 issue 里那两个待查方向都可以排除:AuditPlugin 有通过 manifest 注册进 schema sync,dev 组合也没有漏装配任何 service/对象包。

    2. 真实根因:ambient 事务跨数据源泄漏

    同一次 boot,一次 PUT /api/v1/meta/api/... 之后的完整计数:

    • sys_audit_log insert 尝试 52 次,成功 50,失败 2
    • 失败的 2 次堆栈全部带 knex trxClient.query / execution/transaction.js 帧;成功的 50 次一次都没有

    机制:protocol.saveMetaItem 在主数据源开事务 → 事务内写 sys_metadata 触发审计钩子 → getDriver('sys_audit_log') 正确解析到 telemetry 驱动,但 buildDriverOptions(packages/objectql/src/engine.ts:1609)把 txStore 里属于主库连接的事务句柄无条件塞进 driver options → knex .transacting(trx) 把语句发去主库执行 → 主库没这张表。

    ADR-0067 D2「加入已开启的 ambient 事务」被应用时没有校验该事务是否属于同一个 driver。

    3. 影响面比 issue 描述的宽

    不限于元数据写。POST /api/v1/batch + transaction: true 写一条普通业务记录也丢:

    Audit write failed {"object":"showcase_category"}
    

    凡是在事务中执行的被审计写入,合规审计行都会丢失 —— 只要部署启用了 lifecycle 数据源分流(os dev 默认开,生产设了 OS_TELEMETRY_DB 同理)。受影响的是每一个 lifecycle class 为 audit/telemetry/event 的对象,不止 sys_audit_log。

    4. 已交付 / 未交付

    PR #5350(draft)只修了本 issue 的第二个论点 —— 日志级别:WARN Audit write failed 升为 error(带后果+修复方向,全进程只报一次),写入点提取为具名 persistAuditTrailRow 并登记进 check:durability-log-level 焊死级别。这一条完全在本单声明的文件面内。

    主缺陷未修。 修复点在 packages/objectql/src/engine.ts 的事务管道,属 engine 车道(本单是 identity 借道),且涉及一个本 issue 未给出结论的架构取舍:

    跨数据源写入在外层事务回滚时,应当 (A) 一起回滚(需要两阶段/跨库事务),(B) 自动提交、接受可能的孤儿审计行,还是 (C) 直接拒绝并让调用方显式选择?

    审计账本大概率倾向 (B)(宁可多记),但这会改变 ADR-0067 的事务语义、影响所有跨数据源写入,不是审计插件能单方面决定的。故此处停手,上报 needs_decision。

    建议:本 issue 改标题与归属(engine 车道),PR #5350 合并前把 Fixes 改成 Refs,避免主缺陷随之关闭。


    Generated by Claude Code

  4. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    PM 复核:ACCEPT(仅日志级别半边) → PR #5350;主缺陷前提被证伪,已转 #5351

    会话 session_01FTszibd6C8sUCCZnM4VcrL。

    一、本单主前提被证伪 —— 这是一次成功的派发,不是失败

    dev 做了真实复现而非只读代码,结论是本单标题所述不成立:sys_audit_log 表是建了的,在 dev.telemetry.db 里,50 行。lifecycle.class: 'audit' 被 ADR-0057 §3.6 路由到专用 telemetry 数据源,os dev 默认以兄弟 SQLite 文件提供一个。本单列出的两个待查方向双双被排除:AuditPlugin 确实通过 manifest 注册进 schema sync;dev 组合也没缺任何包。这是 #4887 记录过的「表在另一个 store 里」误诊形状再现。

    真因是别的东西,而且更严重:buildDriverOptions(packages/objectql/src/engine.ts:1609)把 ambient 事务句柄无条件注入,不校验它属于哪个数据源 —— 主库上开的事务,句柄被交给 telemetry 驱动,语句于是发到主库执行。PM 已在 origin/main 独立核实该代码,其注释自陈「ambient is the safety net」,漏的正是这张网。

    影响面远宽于本单:POST /api/v1/batch + transaction: true 写普通业务记录同样丢审计行 —— 凡在事务中执行的被审计写入,合规审计行全部静默丢失,涉及每一个 lifecycle class 为 audit/telemetry/event 的对象。已立 #5351(domain:engine,needs-user-decision),含完整证据、四选项分析与推荐。

    dev 在真正的修复点前停手上报而非硬修,是对的:落点在 engine 车道、超出本单借道声明的文件面,且涉及一个本单未给结论的语义取舍(外层事务回滚时孤儿审计行怎么办)。

    二、日志级别半边:ACCEPT

    这一半完全在车道内、判据明确,PR #5350 落地得干净:

    • 判据用对了:按 AGENTS.md「Degradation log levels」那一个问题 —— 被审计的写入本身成功、数据在盘上、接口 200,从外面看一切正常,只有记录「谁做的」那行没落地且无人重试 → durability 类 → error。
    • error 同时给出后果与修复方向,且测试断言了这一点(/compliance trail is now INCOMPLETE/、/SUCCEEDED/、/nothing retries it/、/telemetry/、/OS_TELEMETRY_DB=0/)—— 不是「报个错就算」。
    • 只报一次,后续降 debug。测试断言 5 次失败 → 1 条 error + 4 条 debug + 0 条 warn。理由写得对:审计写发生在每一次数据变更上,逐次报错正是当初让 [automation/approvals] 进程重启后审批决策静默失效:挂起 flow run 仍只存内存(#1518 标记 COMPLETED 但 17.0.0-rc.1 未生效),approve 落库却永不推进且零报错 #4420 的 warn 无人阅读的反射。
    • 级别被门禁焊死:写入点提取为具名 persistAuditTrailRow 并登记进 DURABILITY_CRITICAL_CALLEES,check:durability-log-level 守着 —— 声明即强制,防止日后被悄悄改回 warn。反向验证也做了:改回 warn → 门禁如期变红并精确点名 audit-writers.ts:729。
    • 重构无行为变化:PM 核对了 persistAuditTrailRow 抽取前后的调用点,activitiesEnabled 守卫等价保留,try 块内位置移动不影响语义。
    • dev 如实标注第 4 个测试(never lets a logging failure break the audited write)改动前后都绿 —— 它钉的是本来就成立的性质,保留是防未来重构,没有凑成「全红转全绿」。

    一处精度提示,不作返工项:changeset 写「全进程只报一次」,而 auditFailureReported 是 installAuditWriters 闭包内的标志,实际是每个 audit-writer 安装实例一次。在多租户 host 上每个引擎各报一次 —— 这个行为本身比真正的全进程去重更合理,只是措辞不够精确。属 patch 级 changeset 的措辞小节,不值得为它多跑一轮。

    三、本单去向


    Generated by Claude Code

  5. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    收尾并关闭 —— 可执行内容已全部落地或转出

    会话 session_01FTszibd6C8sUCCZnM4VcrL。PR #5350 已经合并队列合入 main(e18e3da77),落地判据复核通过:

    $ git grep -c "persistAuditTrailRow" origin/main -- packages/plugins/plugin-audit/src/audit-writers.ts
    2
    $ git grep -c "persistAuditTrailRow" origin/main -- scripts/check-durability-degradation-log-level.mjs
    1                       # 门禁词表里在,级别被焊住
    $ git grep -c "logger?.warn?.('Audit write failed'" origin/main -- .../audit-writers.ts
    0                       # 旧的 warn 零残留
    

    本单为什么关闭,而不是留着

    本单的两半各有归宿,没有一半是被丢掉的:

    本单的诉求 归宿
    日志级别应为 error 而非 WARN ✅ PR #5350 已落地(e18e3da77)
    「sys_audit_log 表没建」 ⛔ 前提被证伪 —— 表是建了的,在 dev.telemetry.db 里,50 行(ADR-0057 §3.6 把 lifecycle.class: 'audit' 路由到专用 telemetry 数据源)
    症状本身(每次元数据写 ERROR + 审计静默丢失) ➡️ 真因已转 #5351(engine 域):ambient 事务句柄跨数据源泄漏,buildDriverOptions 不校验事务属主

    标题所述的缺陷不存在,所以本单不能以「已修复」的名义留着;而它真正抓到的症状已由 #5351 承接,且 #5351 的作用域比本单宽得多 —— 不限于元数据写,凡在事务中执行的被审计写入,合规审计行全部静默丢失,涉及每一个 lifecycle class 为 audit/telemetry/event 的对象。

    ⚠️ 给读到这里的人:真正要修的是 #5351,不是本单。 #5351 挂着 needs-user-decision(外层事务回滚时孤儿审计行的语义要维护者拍板,四选项分析与推荐已附),裁决前任何人都不该派。

    借道注销

    本单是本会话向 identity 域(packages/plugins/plugin-audit)的单单借道,随 PR #5350 合入即结束。登记表 #4604 同步注销。#5351 属 domain:engine,按跨车道协议留给 engine 车道,本会话不派。


    Generated by Claude Code

  6. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    Contributor

    根因指引(后补,给下一个搜到本单的人):本单的可见症状(元数据写后成对的 ERROR Insert operation failed {object: sys_audit_log} + WARN Audit write failed)的真实根因是 #5351 —— ambient 事务句柄跨数据源穿透:审计写在 saveMetaItem 的事务内被交给了错误连接。已由 PR #6171(2026-08-07)根因修复:审计类系统账本(声明 lifecycle.class ∈ audit/telemetry/event)经同源校验移出跨源事务、在属主连接上落盘。

    另注:本单自陈的前提(「dev --fresh 上 sys_audit_log 表根本没建」)已被 #5351 实测证伪 —— 表建在 dev.telemetry.db、行在盘上,属 #4887 记录过的「在另一个 store 里」误诊形状;其列出的两个待查方向均被排除,日志级别半边已由 PR #5350 单独落地。本单当时以 completed 关闭是对的,理由以本条为准。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions