You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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 #2755removed 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.
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 inobjectui— ever reads or writes:visibilityField.select(['public','internal','private'], { defaultValue: 'public' })enforceFeedsCapability, not the record-level gates added in #4630, not the REST layer, notobjectui'sRecordDetailViewdiscussion panel. A comment markedprivateis exactly as visible as apublicone.reply_countField.number({ defaultValue: 0, readonly: true })parent_idlookup; nothing counts them on either side. Every row'sreply_countis0forever, and the field isreadonlyso an author cannot even set it by hand.Why it matters
This is the exact shape of the Prime Directive #10 trap:
visibilityreads as an access-control lever on a collaboration object. An app author (or an AI authoring metadata) who setsvisibility: '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_countis milder but the same class: a UI that binds a "N replies" badge to it renders0forever.Precedent — this exact pair was already resolved once, next door
sys_attachmentcarriedshare_typeandvisibilitywith 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 (seepackages/services/service-storage/CHANGELOG.md, thef531a26entry: "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 itsthread_idnames, so a per-rowvisibilityenum 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'sexternalSharingModel, not invented ad hoc).Decision needed (maintainer)
For each field, enforce or remove:
visibility— (a) REMOVE, following thesys_attachmentprecedent (thespec-property-retirementplaybook 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 whatinternalandprivatemean relative to OWD/sharing and to external principals. Recommendation: (a) remove — an undesigned enum on a security surface is a liability, andsys_commenthas 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 countparent_idchildren; or (b) MAINTAIN it withafterInsert/afterDeletehooks onsys_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_attachmentremoval chose to leave the DB columns as unmanaged leftovers with no migration.