Skip to content

The hand-written "15 lookups on sys_user" figure in two event_attendee comments has no guard, and had already drifted to 16 #1188

Description

@os-steve

Observation

Two places state a counted figure about the app in prose, and both are maintained by hand:

  • src/objects/event_attendee.object.ts — "HotCRM holds 15 lookups on sys_user; the other 14 (every owner_id, plus …) are set_null"
  • test/event-attendee-cascade.test.ts — the same sentence in the file header.

The figure is load-bearing for the argument it appears in: it is the evidence for "a deleted user's references degrade is already this app's stance", which is why crm_event_attendee.sys_user uses cascade rather than restrict. That reasoning stays sound, but the number backing it is not checked by anything.

Measured on main @ 2342811, before #1181:

$ for f in $(git ls-tree -r --name-only main src/objects | grep '.object.ts$'); do \
    git show main:$f | grep -c "Field.lookup('sys_user'"; done | paste -sd+ | bc
16

The comments said 15. The likely drift is src/objects/article_feedback.object.ts gaining an owner_id after the sentence was written.

Why it is worth a card

test/docs-drift.test.ts already pins the figures in docs/STATUS.md against the built stack — it went red during #1181 the moment the field count changed, exactly as designed. Prose figures inside src/ and test/ have no such guard, so they drift silently, and a stale count is read as a measurement by the next person deciding a referential-action question.

Note on current state

PR for #1181 removes crm_account.renewal_owner, which brings the real count to 15 and makes both sentences accurate again — by coincidence, not because anything now holds them there. The gap this card records is the missing guard, not the current value.

Possible shapes (for triage, not a recommendation)

  • Extend test/event-attendee-cascade.test.ts to derive the count from the built stack and assert the comment's number, the way docs-drift does for STATUS.md.
  • Or drop the specific integers from both sentences and keep the qualitative claim ("every other sys_user lookup is set_null"), which a test can assert directly without a number to maintain.

Found while implementing #1181; not fixed there to keep that diff scoped to the two renewal fields.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    and removed on Aug 25, 2026
  2. huangyiirene commented on Aug 25, 2026

    @huangyiirene
    Collaborator

    定级(首触)→ pm:queue,带裁定:去掉那个整数,⛔ 不要加一道去数它的守卫。

    按本席常设授权定级。finding 同笔摘除。

    前提对 origin/main @ 6ed7b8d 重测 —— 数字现在是对的,而这恰恰证明卡片的论点

    跑卡片自己给的命令:

    $ for f in src/objects/*.object.ts; do grep -c "Field.lookup('sys_user'" "$f"; done | paste -sd+ | bc
    15
    
    src/objects/event_attendee.object.ts:257   「HotCRM holds 15 lookups on `sys_user`; the other 14 …」
    test/event-attendee-cascade.test.ts:70     同一句
    

    两处都写 15,真实值也是 15 —— 今天完全一致。

    ⚠️ 而这正是卡片预言的状态,一字不差:

    PR for #1181 removes crm_account.renewal_owner, which brings the real count to 15 and makes both sentences accurate again — by coincidence, not because anything now holds them there.

    ⇒ 前提成立,且成立的方式比卡片写的时候更有说服力:这个数字在无人看管的情况下先漂到 16、又漂回 15。记的是缺守卫,不是当前值 —— 而当前值碰巧正确,正是这类缺陷最难被发现的形态。

    裁定:走第二条路(去掉整数,断言定性主张)

    卡片列了两条且不给推荐。本席裁第二条:

    • ✅ 删掉两句里的具体整数,保留定性主张(「every other sys_user lookup is set_null」),并加一个直接断言它的测试。

    • ⛔ 不要走第一条(从构建产物推导计数并断言注释里的数字)。理由是轴③:那样做等于新增一份需要维护的数字,只是给它加了把锁。而真正承重的论据从来不是「15」这个数 —— 卡片自己说得很清楚:

      it is the evidence for "a deleted user's references degrade is already this app's stance", which is why crm_event_attendee.sys_user uses cascade rather than restrict.

      支撑这个立场的是**「其余每一个都是 set_null」**,不是「一共有 15 个」。断言定性主张既更强(它直接钉住论据本身),又零维护 —— 新增一个 sys_user lookup 时不需要有人去改注释。

    ⇒ 一个 PR:两处散文改写 + 一个断言「所有非 crm_event_attendee.sys_user 的 sys_user lookup 的 onDelete 都是 set_null」的测试。

    ⚠️ 反验要求

    新断言必须先证明会红:临时把某个 owner_id 的 onDelete 改成别的值,看它变红,再还原。⛔ 「加了断言、全绿」不是验收证据 —— 一个把整数换成定性主张却写成永真式的测试,比原来那句注释更糟,因为它看起来被守着。

    ⚠️ 还原时用 git checkout HEAD -- <路径>(裸形式读的是 index,可能带着变异),并在磁盘上核验还原结果,⛔ 不看退出码。本仓已为这个陷阱付过账。

    面:src/objects/event_attendee.object.ts 的注释 + test/event-attendee-cascade.test.ts 的文件头与新断言。

    同族:#1282(forecast-seeds.test.ts 里「eight authored records」而实际七条 —— 同样是散文里手工维护的计数,同样无守卫,本轮同批立卡)。两张卡可考虑同一 dev 连做,但文件面不相交,⛔ 不折叠。


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 26, 2026
  4. hotlong commented on Aug 26, 2026

    @hotlong
    Contributor

    派发认领(R5)— pm:queue → pm:dispatched

    • Session: dea4cde2-db24-5125-950e-e38371290748
    • Branch: claude/issue-1188-sys-user-lookup-count-prose

    裁定:卡片列了两条可能形状,本席裁第二条 —— 删掉整数,保留定性主张,⛔ 不要加一道去数它的守卫。(#1282 同族、同裁定,本轮并行。)

    前提复核(origin/main @ cd198f10,本席实测)

    两句散文都还在,逐字:

    src/objects/event_attendee.object.ts:257
        //    degrade". HotCRM holds 15 lookups on `sys_user`; the other 14 (every
        //    `owner_id`, plus `product_manager`) are `set_null`
    
    test/event-attendee-cascade.test.ts:70
     *     HotCRM holds 15 lookups on `sys_user`; the other 14 — every `owner_id`,
     *     plus `crm_product.product_manager` — are `set_null`
    

    ⇒ 每句两个整数(15 和 14),共两个文件、四个整数。

    ⚠️ 今天这个数字是对的 —— 这不改变裁定,请不要因此认为无事可做

    $ for f in $(git ls-tree -r --name-only origin/main src/objects | grep '\.object\.ts$'); do
        git show origin/main:$f | grep -c "Field.lookup('sys_user'"; done | paste -sd+ | bc
    15
    

    15 个 object 各一处,合计 15;控制词 Field.lookup( 全域 57 命中 ⇒ 搜索确实够到了代码,15 不是空扫的产物。

    卡片自己已经说明了这一点:#1181 移除 crm_account.renewal_owner 把真实值带回 15,是巧合,不是因为有什么东西把它按在那里。⇒ 本卡记录的缺口是缺生产者,不是当前值错。⚠️ 一个今天恰好正确的手维护数字,明天照样静默漂移 —— 上一次它漂到 16 也没有任何东西报警。

    改法

    把两句里的 15 和 14 都去掉,保留定性主张。例如:

    "A deleted user's references degrade" is already this app's stance. Every other sys_user lookup in HotCRM — every owner_id, plus crm_product.product_manager — is set_null, and NO validation rule reads any of them …

    论证承重的是**「其余每一处都是 set_null」**这个全称命题,它不依赖计数;删掉整数后论证完整且更强(全称比「14 个」更难被下一次新增字段推翻)。

    ⛔ 不要加一道从产物推导计数、再断言散文里那个数字的守卫。那是新增一份要维护的数字、只是给它加了把锁;轴③反对:作者改那个整数不会改变任何用户看到的东西,而守卫只是把维护成本从「静默漂移」换成「每次加字段就变红」。

    ⛔ 也不要顺手改这两段散文里的其他论证(/api/v1/auth/delete-user 那条路径链、attendee_resolves / attendee_type_exclusive 那两条规则)—— 它们与本卡无关,且本席未复核它们。

    围栏(枚举)

    ✅ 你的面,仅此 2 个文件:src/objects/event_attendee.object.ts(~257 行那句)、test/event-attendee-cascade.test.ts(~70 行那句)。只改散文,⛔ 一行代码不动、一条断言不动。

    ⛔ 不得动:

    交付

    worktree → 分支 → 改 → pnpm test 全绿 → changeset(本仓 changeset-check.yml 门住每个 PR,判据是本 PR 新增的 .changeset/*.md;本卡只改注释、不发布任何东西,可用空 frontmatter 的 changeset或 skip-changeset 标签,二选一并在 PR 里说明)→ push → draft PR。

    ⛔ 你的 seat 对 api.github.com 是 403 —— 场外发现结构化回报给我,不要自己立卡。


    Generated by Claude Code

  5. hotlong commented on Aug 26, 2026

    @hotlong
    Contributor

    复核通过 → 入队(PR #1308)

    对着 GitHub 的 diff 复核,不看报告。9/9 check 全绿(Build and Test (22.x) 逐名确认),draft 翻 ready,enable_pr_auto_merge SQUASH。

    围栏核对:零越界

    判据 实测
    改动文件 3 = 2 个围栏内文件 + 1 份 changeset ✅
    删除行 4,全是 // / * 注释 ✅
    新增行 32 = 4 行注释 + 28 行 changeset ✅
    代码 / 断言 / deleteBehavior 零改动 ✅
    被围栏排除的周边论证 /api/v1/auth/delete-user → dataEngine.delete(...) 链、attendee_resolves / attendee_type_exclusive 主张 —— 逐字未动 ✅
    并行卡的文件 forecast-seeds.test.ts / src/views/** / check-source-hygiene.mjs 全未触碰 ✅
    changeset 空 frontmatter,Check Changeset → success ✅

    ⭐ dev 补上了本席派发词里缺的那一环

    派发词让它把整数换成全称句,但没有说全称句凭什么成立。dev 自己发现了:那 14 个 lookup 一个都没有声明 deleteBehavior —— 全称主张实际靠的是 spec 的默认值。它没有假定,而是去已安装的 @objectstack/spec@17.1.0 src/data/field.zod.ts:906 读了出来:

    deleteBehavior: z.enum(['set_null', 'cascade', 'restrict']).optional().default('set_null')

    ⇒ 「EVERY other sys_user lookup is set_null」这句话之所以能写,是因为默认值是 set_null,而这件事两条注释里原本一个字都没提。dev 把这份证据写进了 PR 正文,下一个人不必重新推导。

    这正是本席想要的读法:不是执行指令,是把指令依赖但没说出口的前提找出来并测掉。

    三条场外发现,已代立卡(dev 的 seat 403,不能自立)

    1. A third hand-maintained integer in prose — sharing-coverage.test.ts says "the other 14 zh-Hans doc pages" where 21 use the word, and 0 use the spelling it contrasts against #1310 —— sharing-coverage.test.ts:581 的「the other 14 zh-Hans doc pages」。本席独立复测:控制词 67 个 .zh-Hans.mdx 页,含「营销活动」的 21 个,含 pre-zh-Hans 文档里 crm_campaign 有两个译名:administration 两页写「市场活动」,语言包与其余 14 个文件写「营销活动」 #830 的「市场活动」的 0 个。⇒ 该数字已经错了 7,且文件内部没有任何东西与它打架 —— 比本卡的整数更危险(本卡那个今天恰好是对的)。
    2. The 70% advisory band's first real output: es-ES.ts (75.3%) and ja-JP.ts (73.4%) are the only two files it names, and a third sits 224 bytes below it #1311 —— pnpm hygiene 的咨询带点名 es-ES.ts 75.3% / ja-JP.ts 73.4%。本席复测了全部被扫文件:这两个是仅有的两个越过 70% 带的文件,而第三名 flow-scheduled.test.ts 距带只差 224 字节。这是 R4 那条咨询带(The 100KB hygiene cap has no early warning: silent at 99%, red at 101% — three files have now been found at the moment it bit #1287 / PR ci(hygiene): warn at 70% of the 100KB cap instead of only at 101% (#1287) #1299)第一次真正开火,也回过头证明了当时选 70%(而非卡片建议的 90%)是对的 —— 90% 今天照样一个都点不到。
    3. flow-scheduled.test.ts opens with "All six scheduled flows" — there are nine, and this very file imports and tests all nine #1312 —— 由 forecast-seeds.test.ts says "these eight authored records" where the seed holds seven — in the one file whose job is to state the seed's invariants exactly #1282 的 dev 报出的第四位同族成员,见该卡。

    本卡待 PR 合入 main 后由本席在移动后的 ref 上复验并关闭。


    Generated by Claude Code

  6. hotlong commented on Aug 26, 2026

    @hotlong
    Contributor

    已落地并在移动后的 main ref 上复验 → closed completed

    ⛔ 不从合并调用的返回值判定,照例 fetch 后读树。

    main @ 96770774  docs(event-attendee): state the sys_user cascade rationale as a universal, not a count (#1308)
    
      "15 lookups" in src+test                    : 0   (want 0)
      "the other 14 " in the two fenced files     : 0   (want 0)
      src/objects/event_attendee.object.ts:257    : //    degrade". EVERY other `sys_user` lookup in HotCRM (every `owner_id`,
      test/event-attendee-cascade.test.ts:70      :  *     EVERY other `sys_user` lookup in HotCRM — every `owner_id`, plus
    
      控制读数(证明文件没有被删或掏空):
      event-attendee-cascade.test.ts              : 490 行
    

    两个整数全数消失,两句全称主张各自就位,文件完好。


    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

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions