feat(moderation): show removed text and comment root posts in the modlog master
Admins review removed content from the modlog, but after a removal every read path served a tombstone and the log carried no content, so nobody could see what was removed. Comment entries also could not link to their thread, because a comment URI alone does not build a client route. The user decided on 2026-10-13 that the snapshot is public, except for doxing and illegal-content removals, so the modlog stays public. Changes: - Migration 056 adds moderation_content_snapshots (keyed by action ID, cascade on action delete; author_did, title, text, link_url, captured_cid, author_deleted_at) and moderation_actions.subject_root_uri. - removeContent stores, in the removal transaction, the post title, body and external link URL, or the comment body, read from the CID the admin observed. No media blobs or blob references are copied. - Illegal-content removals store no snapshot. A doxing or illegal-content removal also deletes every snapshot earlier removals of the subject stored, for admins too. - Removing a comment its author already deleted stores no snapshot. Labels, label retractions and restores never store one. - listActions returns contentSnapshot only while the subject is public and not deleted by its author. listAdminActions returns it whenever a row exists, including for doxing removals and author-deleted subjects. - An author delete of a comment marks its snapshots author_deleted_at, in the same transaction as the soft delete, so a re-created comment at the same URI does not bring the text back to the public log. SoftDeleteWithReasonTx now opens its own transaction when given none. - Account erasure deletes the author's snapshots (by posts.author_did or comments.commenter_did, so legacy posts in a community repository are covered) and keeps the action rows. - Comment actions record subject.rootUri from comments.root_uri at action time; a restore copies it from its removal. A root that is not a valid AT-URI is not recorded, and a link URL that is not a valid URI is dropped. - The public log omits rootUri when the root post has a doxing or illegal-content action or is no longer indexed; listAdminActions then carries it in privateSubject. - docs/MODERATION.md documents the snapshot and rootUri rules. - Tests: unit projection tests, lexicon fixtures, Postgres integration tests for capture, erasure and visibility, and HTTP modlog output. Client notes: - Lexicon social.coves.moderation.defs adds #contentSnapshot {title?, text?, linkUrl?, capturedCid}, an optional contentSnapshot on #actionView and #adminActionView, and an optional rootUri on #subjectRef. All are additive. - contentSnapshot is returned by listActions and listAdminActions only, never in mutation results. - A restore entry carries no snapshot. Clients show the snapshot of the removal it reverses (reverses.actionId). - Snapshot text is author-written. Render it as plain text, and sanitize linkUrl before making it a link: it is only checked to be a syntactic URI, so it may use any scheme, including javascript:. - Actions recorded before this migration have no snapshot and no rootUri. - Update the "Coves Moderation: Client Handoff" doc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>