Skip to content

sys_comment.visibility and sys_comment.reply_count are declared but nothing anywhere reads or maintains them (ADR-0049 enforce-or-remove) #4756

Description

@os-zhuang

Found while implementing #4630 (record-level authorization for sys_comment). Filed unassigned, out of that PR's scope.

What

sys_comment (packages/plugins/plugin-audit/src/objects/sys-comment.object.ts) declares two fields that no code path in this repo — or in objectui — ever reads or writes:

field declaration reality
visibility Field.select(['public','internal','private'], { defaultValue: 'public' }) Never read. No gate consults it: not enforceFeedsCapability, not the record-level gates added in #4630, not the REST layer, not objectui's RecordDetailView discussion panel. A comment marked private is exactly as visible as a public one.
reply_count Field.number({ defaultValue: 0, readonly: true }) Never incremented. Nested replies are modelled by the parent_id lookup; nothing counts them on either side. Every row's reply_count is 0 forever, and the field is readonly so an author cannot even set it by hand.
$ grep -rn "reply_count" --include=*.ts packages/    # only the schema + its generated translations
$ grep -rn "visibility" packages/plugins/plugin-audit/src/   # only the schema
$ grep -rn "reply_count" --include=*.tsx ../objectui   # nothing

Why it matters

This is the exact shape of the Prime Directive #10 trap: visibility reads as an access-control lever on a collaboration object. An app author (or an AI authoring metadata) who sets visibility: 'private' on a sensitive comment will reasonably believe it is hidden from other users who can read the record. It is not. Declaring an unenforced security-looking key is worse than not declaring it.

reply_count is milder but the same class: a UI that binds a "N replies" badge to it renders 0 forever.

Precedent — this exact pair was already resolved once, next door

sys_attachment carried share_type and visibility with zero runtime consumers, and #2755 removed both rather than implementing them, on the grounds that attachment access derives from the parent record and there is no replacement key (see packages/services/service-storage/CHANGELOG.md, the f531a26 entry: "Both fields were modeled in v1 with zero runtime consumers (ADR-0049 parsed-but-unenforced). There is no replacement key: attachment access is derived from the parent record by the hooks above.").

After #4630 that argument transfers verbatim to sys_comment.visibility: a comment's access now derives from the record its thread_id names, so a per-row visibility enum has no coherent meaning layered on top of it — unless someone deliberately designs one (e.g. internal = hidden from external/portal principals, which would need to be defined against ADR-0090 D11's externalSharingModel, not invented ad hoc).

Decision needed (maintainer)

For each field, enforce or remove:

  • visibility — (a) REMOVE, following the sys_attachment precedent (the spec-property-retirement playbook applies: tombstone + changeset with the FROM/TO, and note these are object fields, not spec keys, so the kit is lighter); or (b) ENFORCE, which first requires deciding what internal and private mean relative to OWD/sharing and to external principals. Recommendation: (a) remove — an undesigned enum on a security surface is a liability, and sys_comment has no record-level authorization: any org member reads and writes comments on records they cannot see #4630's parent-derived rule is the coherent answer to "who sees this comment".
  • reply_count — (a) REMOVE, and let consumers count parent_id children; or (b) MAINTAIN it with afterInsert/afterDelete hooks on sys_comment (a roll-up, cheap and self-contained). Recommendation: (b) maintain if any UI wants the badge, otherwise (a); this one is a genuine convenience denormalization, not a security claim.

Note that removing either field is a stored-data question as well as a schema one — existing rows carry the columns, and the sys_attachment removal chose to leave the DB columns as unmanaged leftovers with no migration.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions