feat(plugin-audit)!: sys_comment 的 visibility / reply_count 退役 —— ADR-0049 enforce-or-remove,两键都移除 (#4756) - #4849
Merged
Conversation
…, ADR-0049) Both fields were declared with zero runtime consumers — nothing in this repo, in objectui, or in cloud ever read or maintained either one. Maintainer decision on the enforce-or-remove question: remove both. Same disposition and the same stated reason as sys_attachment.share_type / .visibility in #2755 ("attachment access is derived from the parent record"). - visibility: a security-LOOKING key with no gate behind it — not consulted by enforceFeedsCapability, the #4630 record-level gates, the REST layer, or objectui's discussion panel, so a comment marked `private` was as visible as a `public` one (Prime Directive #10 in its textbook shape). No replacement key: after #4630, who can see a comment derives from the record thread_id names. An external/portal meaning for `internal` needs ADR-0090 D11 externalSharingModel first, and can return enforce-first. - reply_count: never incremented and readonly, so every row read 0 forever. Deliberately NOT replaced by an afterInsert/afterDelete roll-up — the predicate/bulk write-hook gaps tracked by #4770 / #4778 / #4779 are exactly where such a counter drifts, and a drifting counter is worse than none. Count parent_id children at read time instead. Existing DB columns stay as unmanaged leftovers, no migration (as in #2755). plugin-audit's four translation bundles regenerated by the documented extractor; a pin test fails loudly, prescription included, if either name is re-declared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
…-comment-dead-fields
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 3, 2026 09:55
os-zhuang
enabled auto-merge
August 3, 2026 09:55
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4756
sys_comment上两个「声明了但没人读、没人维护」的字段按 ADR-0049 enforce-or-remove 移除。维护者裁决:两键都移除,reply_count明确不走 rollup hook 路线。为什么移除
visibilityenforceFeedsCapability,不是sys_commenthas no record-level authorization: any org member reads and writes comments on records they cannot see #4630 加的记录级 gate,不是 REST 层,也不是 objectui 的讨论面板。标成private的评论,对所有能看到该记录的人完全一样可见 —— 作者(或写 metadata 的 AI)会合理地相信它被藏起来了,拿到的却是静默安全失效,而不是一个报错。sys_commenthas no record-level authorization: any org member reads and writes comments on records they cannot see #4630 之后「谁能看这条评论 = 谁能看thread_id指向的那条记录」是唯一连贯规则;把一个未设计的 per-row 枚举叠在上面,等于给同一个问题造第二个真相源。externalSharingModel落地 —— 今天没有可以对之隐藏的人。这不封死未来:portal 落地时它可以带真实闸门 + 测试、enforce-first 地回来。为什么
reply_count也移除(而不是补 rollup hook)readonly: true让作者连手填都做不到,所以每行永远读作0;绑「N 条回复」徽章的 UI 永远渲染 0。bindApprovalLockHook对谓词式(multi)更新完全失效:if (!id) return把「没解析到行」当成「允许」 #4778 / 共享规则 hook 对谓词式(multi)写入不重算:if (!id) return让sys_record_share授权在批量更新后变陈旧 #4779 ——if (!id) return把批量操作整个放过)。靠 hook 维护的计数器正落在这类缺口上,批量删回复后计数不减。会漂移的计数比没有计数更糟 —— UI 和 AI 都会信任它。parent_id聚合。等 hook 的condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775 一族把批量 hook 语义收干净后,若性能需要再引入设计过的 rollup。#2755 先例逐字对照
sys_attachment的share_type/visibility是同构的一对:v1 建模、零运行时消费者,#2755 选择移除而非实现,理由是「attachment access is derived from the parent record」(packages/services/service-storage/CHANGELOG.md的f531a26条目)。#4630 之后这个论证逐字迁移到sys_comment.visibility。存量列的处置也照抄:留作 unmanaged leftover,不做数据迁移。消费方复核(裸名扫描,不是 import 扫描)
三仓扫描,读数如下 —— 没有任何真 reader:
reply_countvisibilitydocs/adr/0052表格一行/workspace/objectui,git -C扫描)replyCount,是FeedItem的前端本地状态:RecordDetailView回复时自己+1,ThreadedReplies直接replies.length;从sys_comment拉行的 map 只读id / author_name / author_avatar_url / body / created_at / updated_at / parent_id / reactions—— 不读reply_count,create 负载也不发visibility的命中全是 view/section/tab 的visible条件与 record-alert 谓词,与本对象无关)objectui 的 create 负载同样从不写
visibility。形态:这是 object field 级退役,不是 spec key 退役
kit 比 spec 轻,以实跑门禁为准,没有照搬
retiredKey():retiredKey()/UNKNOWN_KEY_GUIDANCE都是 spec 属性的设施),liveness ledger 也只登记 spec 属性,没有 sys_comment 字段条目 —— 与 [attachments] v1 follow-ups: sys_file orphan lifecycle + non-admin permission matrix dogfood #2755 当时一致。sys-comment-retired-fields.test.ts在任一字段被复活时变红,失败信息里直接带处方。node scripts/check-i18n-bundles.mjs --write --filter=plugin-audit重生(merge 模式,四份 bundle 各删 11 行,手写译文全部保留),未手改生成物。plugin-audit 不产metadata-formsbundle(其 extract 配置带--objects-only --no-metadata-forms),platform-objects的*.metadata-forms.generated.ts与本对象无关,未被触及。minor(与 [attachments] v1 follow-ups: sys_file orphan lifecycle + non-admin permission matrix dogfood #2755 在 15.1.0 的处理、以及当前 17.x rc 线上其他移除类 changeset 一致),body 写明 FROM → TO 与存量列不迁移。处方(墓碑写在哪都一致)
visibility→ 评论可见性由thread_id指向记录的记录级权限决定(sys_commenthas no record-level authorization: any org member reads and writes comments on records they cannot see #4630),没有 per-row 覆盖。reply_count→ 读时按parent_id聚合子行。存量 DB 列:不迁移,以及移除后各路径的响亮程度
按 #2755 先例留作 unmanaged leftover。诚实地区分两条路径:
?pageSize=5返回 200 + 空列表 #4134 / REST 列表:sort/select/expand指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226)是从 object schema 解析字段名的,不是从表,所以 filter / sort /select/expand点名这两个字段,任何部署上都是400 INVALID_FIELD,不管遗留列在不在。原来「永远显示 0」的静默错,变成会自报家门的报错。mapDataError归一到同一400 INVALID_FIELDenvelope;老库上遗留列仍会接受写入(validateRecord不拒绝未声明的键),值永远没人读。删列是可选的手工清理,不是升级要求。Pin 测试与 sabotage 实跑
packages/plugins/plugin-audit/src/objects/sys-comment-retired-fields.test.ts:两个字段不在fields里、不在 index / highlightFields / titleFormat / nameField 里,且两条处方各自的替代路径(parent_id/thread_id)仍在。sabotage(把两个字段声明原样加回)实跑 —— 必须红,且失败信息带处方:
恢复后同一文件 5 passed。
ADR-0052 的一行注记
docs/adr/0052§5 的对比表里有一行把reply_count列为「已声明的字段」。决策本身不动(Prime Directive #13),只在表下加一条> Note (#4756),说明该行记录的是决策当时的字段清单、其中reply_count已退役、threading 本身(parent_id)/mentions/reactions 不受影响 —— 免得后来的读者(尤其是 AI)从一张历史表里读出一个已经不存在的字段。验证(全部实跑读数,merge
origin/main之后重跑)pnpm build --concurrency=2→Tasks: 71 successful, 71 totalpnpm --filter @objectstack/plugin-audit test→Test Files 7 passed (7) / Tests 94 passed (94)pnpm --filter @objectstack/plugin-audit typecheck→ 无输出(clean)turbo run typecheck --concurrency=2→Tasks: 122 successful, 122 totalpnpm check:i18n→OK (9 package(s), all bundles in sync).pnpm check:i18n-coverage→OK (12 config(s), 660 baselined untranslated string(s), none new).@objectstack/restTest Files 38 passed / Tests 573 passed;@objectstack/metadata-protocol31 passed / 260 passed;@objectstack/platform-objects9 passed / 266 passedpackages/spec生成物(本 PR 不改 spec),四张 ratchet 无改动。content/docs/releases/一个字未碰。草稿状态,不 auto-merge、不自行 ready;入队由 PM 决定。
Generated by Claude Code