Problem/Motivation

ACT fails closed. CommentIndexRepository::load() drops a comment and its whole subtree when any ancestor is not accessible to the viewer, missing, or part of a cycle (src/CommentIndexRepository.php:229-242). An ordinary reader therefore loses published replies from other people whenever a moderator unpublishes the parent. The consequences:

  • The summary disagrees with core's statistics.
  • Unread navigation and the filter cannot reach the hidden replies.
  • Permalinks to them return 404 (validatedIndex(), src/Controller/CommentFragmentController.php:349-353).
  • They are missing from the no-JS view.

The re-parent rule in CommentIndex::__construct() (src/CommentIndex.php:65-67) is effectively dead code.
Deletion cascades through core. CommentDeleteForm::submitForm() calls $comment->delete() (src/Form/CommentDeleteForm.php:131), and core deletes every descendant. The only record is the confirmation text (:66).
Moderators need finer tools for three cases:

  • abuse removal that keeps the valuable replies below;
  • spam removal of a whole subtree, with a record of what was removed;
  • takedowns.

This is the decision proposed in ADR 0008 (docs/adr/0008-replies-under-hidden-parents.md).

Proposed resolution

Per ADR 0008:

  1. Structure is shown; content stays access-gated. A parent the viewer may not view renders as a content-free stub:
    • no field values, and no real comment ID (an opaque key, stable within one response);
    • no actions, permalink, unread state or filter match;
    • the relation line reads "Reply to a removed comment";
    • not counted in the summary, but childCounts count through it;
    • omitted when it has no visible descendants.

Each reply keeps its own access('view'). Cycles stay dropped and are logged.

  1. Redact (new permission redact comments, restrict access: true):
    • keeps the row, ID, pid, thread, host, field, created and status;
    • clears the subject, all configured fields, name, mail, homepage and hostname (and uid: proposed, to be decided);
    • records a redaction marker (who, when) in a new base field;
    • is irreversible, and the confirmation says so.
  2. Delete with replies (new permission delete comments with replies):
    • required for ACT's inline Delete on a comment that has replies;
    • before delete(), writes an audit entry per deleted comment (cid, uid, created, actor, host, field, parent) and a summary entry through the advanced_comment_threads logger channel;
    • deletion still goes through CommentInterface::delete().

Every consumer that assumes each ancestor is a real, viewable entity must handle stubs before the ancestry filter is relaxed. Examples: ancestorsOf() in the filter breadcrumb (CommentFragmentController.php:445), and level/ancestor_ids in CommentSliceRenderer.php:288-289.

Remaining tasks

  • Decide ADR 0008's open questions:
    1. hard-deleted parents (stub, re-parent, or fail closed);
    2. GDPR erasure and audit-log retention;
    3. stub semantics (permalink in context, unread, summary, moderator labels);
    4. per-viewer differences from contrib "view own unpublished" modules.
  • Decide whether uid is kept on redaction.
  • Implement the stub representation across index, settings, refresh payload, filter breadcrumbs, relation line, ancestry path, unread navigation and the no-JS view.
  • Redact form and route, base field, permissions, post update and release note. Existing sites keep cascade delete for anyone with core delete access until the new permission is granted.
  • Tests:
    • no leak of the parent's content or ID in markup, settings or refresh;
    • stubs are omitted without visible descendants;
    • audit entries are written before delete();
    • permission combinations.

User interface changes

  • Stub rows ("Reply to a removed comment" / "Removed by a moderator").
  • A new Redact action.
  • Delete on comments with replies becomes permission-gated.

API changes

  • Two new permissions.
  • Stub keys in parentById, orderedIds and refresh payloads (opaque, non-numeric).
  • Audit log entries on the advanced_comment_threads channel.

Data model changes

New base field on comment for the redaction marker, with a post update.

Related issues

#3623698
#3622148
#3622150

Comments

freelock created an issue.