Skip to content

tech-debt/tracking: 让「特权内部写入必须显式声明 isSystem」成为可检查契约(先立约束 → 增量迁移 → 收敛强制) #3166

Description

@os-zhuang

Path: access-security

Restart-when: a second #3164-shape defect is filed (an internal privileged write silently stripped of isSystem by the engine), or the maintainer rules step 1 between the two engineering options (AST lint vs an explicit actor API)

概述

ExecutionContext.isSystem 是引擎级安全强制(#2948 readonly UPDATE 剥离、#3004 owner 锚点、租户写墙等)的闸门:外部用户写入走非 system → 被约束;内部/系统写入应显式带 isSystem → 豁免。但大量核心内部写入方并不显式声明 isSystem,而靠「反正我不经过外部入口」隐式获得信任。

这条隐式信任已经反复咬人:

为什么重要(对齐「让 AI 写代码、少犯错」)

重构后的三步计划(替代「照单迁移 40 处」)

不要把这理解为「机械补 40 处 isSystem」——那无当下收益、且每次改都可能顺带翻转某表的 FLS/owner 行为(把写入方改 system 会绕过这些),为了少犯错反而可能犯错。按下面三步、按安全相关性排序:

步骤 1 —— 立「可检查的契约」(最高杠杆,但需选对工具)

目标:框架内部包里对业务表的 engine.insert/update/delete,要么显式带 isSystem,要么显式标注面向用户,新增违例在 CI / 发布期失败。

⚠️ 实现须知(本 issue 探索得到的关键结论):这不能用 check-role-word.mjs 那种正则计数 ratchet 来做。「是否 thread 了 isSystem」是语义属性(context 经变量传入、跨行、receiver 是否真是 IDataEngine),正则判不准——假阳性会训练大家盲目 --update,假阴性给出虚假信心,反而制造 #2948/#3003 那类「false compliance」。可行形态二选一:

  • (a) AST / 类型感知的 lint(用 ts-morph / typescript 分析:receiver 类型是 IDataEngine,第三参 options 的 context 是否含 isSystem),配 baseline ratchet 冻结现有 ~40 处、只挡新增。工作量真实,但一次到位。
  • (b) 类型/API 层强制:让引擎写方法要求一个显式的 actor 参数({ actor: 'system' | ExecutionContext }),使「不声明身份就调不通」由编译器保证,无需 lint。最干净,但触及所有调用点,改动更大。

步骤 2 —— 增量迁移(在契约之后)

按碰 readonly / owner / 审批类列的写入方优先迁移(better-auth adapter 已在 #3164 完成),每个配测试。碰不到安全相关列的写入方不必为统一而改。

步骤 3 —— 收敛强制

覆盖够高后,把 #3043 的 INSERT 剥离从入口下沉回引擎、删掉入口特例,两处合一,并堵上「插件 hook 里非 system 写入」这类入口拦不到的引擎内路径。

当前判定

关联


Generated by Claude Code

Activity

  1. changed the title [-]tech-debt/tracking: 核心内部写入方未声明 `isSystem` —— 阻碍把 readonly/authz 强制下沉到引擎(与 UPDATE 对称)[/-] [+]tech-debt/tracking: 让「特权内部写入必须显式声明 isSystem」成为可检查契约(先立约束 → 增量迁移 → 收敛强制)[/+] on Jul 18, 2026
  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    存量裁决轮(维护者 2026-08-06 委托,session_01LeEfA7CFwbJb7JJmXm2KM3):裁定停放(pm:on-hold)。前提仍成立(isSystem 契约检查不存在、INSERT 剥离入口与引擎不对称——protocol.ts:1003 经 origin/main 核实),但本单自判不紧急、无活 bug,且步骤 1 需在 AST lint 与显式 actor API 两种工程间先拍板,不是可直接派发的单次任务。重启条件:再次出现 #3164 类「内部写入未显式声明 isSystem 被引擎静默剥离」的真实缺陷,或维护者为步骤 1 拍板并排期。


    Generated by Claude Code

  3. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Triage: Path: access-security; grade none → p2; disposition keep (returned to pm:queue).

    rationale: isSystem is the engine's privilege gate and this card's deliverable is a checkable contract (AST lint or an explicit actor API), not prose. The instrument question is answered: a fleet decision does change — an AI adding an internal writer is refused at authoring time instead of failing silently at runtime, which is precisely the #3164 shape.

    Re-graded against docs/NORTH-STAR.md as part of the maintainer-assigned full-board sweep. pm:on-hold was removed because the charter permits that state only with a machine-readable wake condition. Closures in this sweep are the maintainer's call — this card was kept, not closed.


    Generated by Claude Code

  4. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Triage: closing not_planned under the maintainer-assigned full-board re-grade against docs/NORTH-STAR.md.

    Instrument card — the one question is unanswered. 「它落地后车队的哪个决定或动作会不一样」 has no answer here: it changes what a future code review concludes, not a dispatch that would have gone wrong, a landing that could not land, a review that passed a fake, or a seat that was stuck.

    What is kept, not lost. Closing an issue does not delete it — the body and its whole thread stay readable and reopening is free. The most valuable thing here is a negative result and it stays in the body: a regex/ratchet gate cannot do this job — whether isSystem is threaded is a semantic property (context arrives by variable, across lines, receiver may not be an IDataEngine), so false positives train people to blind---update and false negatives give false confidence. That leaves exactly two viable shapes: an AST/type-aware lint, or an explicit actor parameter.

    Reopening is free if a second #3164-shape defect is filed.


    Generated by Claude Code

  5. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Correction — this seat's batch-1 re-grade on this card is withdrawn

    Triage seat (#6015), session_01Y6AwMBAN8zRUHgmgFdV1Qr. Earlier today this card was re-graded, then closed not_planned. The card has been reopened and its pm:on-hold restored.

    ⛔ The re-grade was made without reading this thread, and it contradicted the ruling recorded here:

    裁定停放(pm:on-hold)…重启条件:再次出现 #3164 类「内部写入未显式声明」(維護者 2026-08-06 委托裁决轮)

    維護者裁決 outranks 北極星 in the priority order, so a North Star re-grade cannot override it. Nothing about this card had changed; the seat simply had not read it.

    Root cause, stated rather than buried. The sweep detector read only the issue body, so every ruling and every Restart-when: line living in a comment was invisible to it. All eight cards touched in batch 1 carry such a ruling; five were closed and all five are reopened. The sweep's 「112 cards carry no wake mechanism」 figure is withdrawn as measured on a broken instrument. The charter already required the fix — 「每张候选读全文 + 全部评论」 — and this seat did not do it.


    Generated by Claude Code

  6. os-litant commented on Sep 19, 2026

    @os-litant
    Collaborator

    Correction — this seat's batch-1 re-grade on this card is withdrawn

    Triage seat (#6015), session_01Y6AwMBAN8zRUHgmgFdV1Qr. Earlier today this card was re-graded, then closed not_planned. The card has been reopened and its pm:on-hold restored.

    ⛔ The re-grade was made without reading this thread, and it contradicted the ruling recorded here:

    裁定停放(pm:on-hold)…重启条件:再次出现 #3164 类「内部写入未显式声明」(維護者 2026-08-06 委托裁决轮)

    維護者裁決 outranks 北極星 in the priority order, so a North Star re-grade cannot override it. Nothing about this card had changed; the seat simply had not read it.

    Root cause, stated rather than buried. The sweep detector read only the issue body, so every ruling and every Restart-when: line living in a comment was invisible to it. All eight cards touched in batch 1 carry such a ruling; five were closed and all five are reopened. The sweep's 「112 cards carry no wake mechanism」 figure is withdrawn as measured on a broken instrument. The charter already required the fix — 「每张候选读全文 + 全部评论」 — and this seat did not do it.


    Generated by Claude Code

  7. os-steve commented on Sep 21, 2026

    @os-steve
    Collaborator

    关闭(维护者指令,skills 席 2 代执行;裁决 #202 B)— 2026-09-21T03:43Z

    出处三件(SKILL.md :149 代执行他人指令,评论带出处三件)— 谁的指令:维护者,在本席(domain:skills seat 2,session_017ETYWqMQD4qMtZzAGovWNi,席位帖 #19287)会话内的真实用户轮次。在哪说:本席会话聊天,2026-09-21,在本席呈交「停放排查」四组清单(全板 165 张停放卡:pm:blocked 64 + pm:on-hold 101;其中 38 张的停放条件已消失——正文与评论里 Blocked-by: / Restart-when: 指向的卡或 PR 全部已关或已合)之后。原话(逐字,⛔ 未翻译、未润色):「还有哪些应该解除停放的你一起排查一下」;对四组清单:「同意」。

    本卡属第二组「条件已消失、但是工具卡 ⇒ 按 #202 B 关闭」:本席于 2026-09-21T03:00Z 机器复核,本卡停放所指向的目标已全部关闭/合并,或其前提已不复存在;而修复落在门禁 / 脚本 / workflow / CI / 席位协议 / PM 工具面,不在产品包——按维护者裁决批次 #202 项 1 字母 B 及其修正「close, never hold」(记录于 #19457):工具卡不带 Unblocks: #N(open 产品卡)或所护已发布面的点名,即关 not_planned,⛔ 不转 hold、不定 p3。

    两条重开条件(任一即可重开进 pm:queue · tooling):① 首行 Unblocks: #N,N 为一张 open 的产品卡;② 卡面点名本修复所护的已发布面。卡上已有的测量与分析原样保留,供重开时续用。


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions