Skip to content

feat: generate short-lived hot topic recaps - #1391

Merged
paulinevos merged 1 commit into
dev-null-sh:productionfrom
paulinevos:hot-headline-recaps
Nov 2, 2026
Merged

paulinevos merged 1 commit into
dev-null-sh:productionfrom
paulinevos:hot-headline-recaps

Conversation

@paulinevos

@paulinevos paulinevos commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Generate a brief, usefulness-gated recap for each hot topic, reusing a snapshot for two hours rather than repeatedly calling the AI providers. This PR contains only the recap backend; headline pages and the Hot topics sidebar are separate.

Changes

  • Queue recap generation for the five hottest headlines. Select approved, available top-level commits from a rolling eight-hour window, using only each author's strongest contribution and at most 100 authors.
  • Run decide on the headline and selected bodies before generation. Skip thin material, allow the writer to abstain, and constrain output to a short, grounded recap without author identifiers, links, or verbatim quotations.
  • Publish only if the selected source bodies remain unchanged under a headline-row lock. Clear affected recaps when source content is deleted, rejected, or its author is erased.
  • Migration: add nullable summary and summary_created_at fields. They are needed to reuse one generated snapshot without repeating AI requests; both stay hidden from model serialization until an explicit surface exposes them. They contain a recap derived from public posts, not stored source-body copies. Expired values are cleared by periodic cleanup, invalidated source content clears them early, and pruning the headline deletes both fields.

Validation

  • Exact recap checkout: php artisan test --compact tests/Unit/Jobs/GenerateHeadlineSummaryTest.php tests/Unit/Jobs/GenerateHotHeadlineSummariesTest.php tests/Unit/Services/HeadlineProviders/OpenAIHeadlineProviderTest.php tests/Unit/Actions/DeleteCommitTest.php tests/Unit/Actions/DeleteCommitContentTest.php tests/Unit/Actions/DeleteUserTest.php tests/Unit/Actions/UpdateCommitStatusesTest.php — 114 passed, 360 assertions.
  • Final UI superset: php -d memory_limit=-1 vendor/bin/pest --parallel --ci --coverage --exactly=100.0 --passthru-php="'-d' 'memory_limit=-1'" — 5,460 passed, 100% coverage.
  • composer test:lint, composer test:types, composer test:type-coverage, bun run test:unit, and the frontend build passed. Node unit tests: 222 passed. Separate targeted component run: seven passed. Existing non-failing lint warnings remain in unchanged tests.

Independent review

PASS, with no unresolved publication blockers. Independent read-only review covered the complete 25-file diff, including concurrency, invalidation, privacy, provider boundaries, failure handling, migration retention, and meaningful tests. The reviewer inspected final verification logs but did not independently run tests.

Reviewed base: 419f602e. Reviewed head: f5e4c7a0.

Limitations

  • Two hours is the freshness/reuse limit, not a strict physical-deletion deadline. Periodic cleanup and queue delay determine when expired text is removed.
  • Real AI output quality and true concurrent database execution were not exercised. Tests use faked provider responses and the local provider boundary.
  • Full-suite validation used the final UI superset; targeted tests ran on the exact recap checkout. Hosted CI is separate from these local results.
  • Visual and device verification are inapplicable: this PR adds no visible UI or endpoint.

Summary by CodeRabbit

  • New Features
    • Hot topics can now receive concise AI-generated recaps based on recent approved posts. Recaps are prepared in the background and are not yet shown in the interface.
    • Recaps may be reused for up to two hours. Only post text and the topic headline are sent to AI providers; account metadata is excluded.
  • Bug Fixes
    • Recaps are cleared when their source posts are removed or rejected, or when an author’s account is deleted.

Copilot AI balanced review requested due to automatic review settings November 2, 2026 19:47
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2d5f1f0f-31cd-4c34-9d34-79dd19c3567c
Commits

Reviewing files that changed from the base of the PR and between 419f602 and f5e4c7a.

Files selected for processing: 25
  • KNOWLEDGE_BASE.md
  • app/Actions/CreateHeadlineSummary.php
  • app/Actions/DeleteCommit.php
  • app/Actions/DeleteCommitContent.php
  • app/Actions/DeleteExpiredHeadlineSummaries.php
  • app/Actions/DeleteUser.php
  • app/Actions/UpdateCommitStatuses.php
  • app/Ai/Agents/HeadlineSummaryWriter.php
  • app/Contracts/Services/HeadlineProvider.php
  • app/Exceptions/HeadlineSummaryFailed.php
  • app/Jobs/GenerateHeadlineSummary.php
  • app/Jobs/GenerateHotHeadlineSummaries.php
  • app/Models/Headline.php
  • app/Queries/HeadlineSummaryCommitsQuery.php
  • app/Services/HeadlineProviders/NullHeadlineProvider.php
  • app/Services/HeadlineProviders/OpenAIHeadlineProvider.php
  • database/migrations/2026_11_02_162353_add_summary_to_headlines_table.php
  • routes/console.php
  • tests/Unit/Actions/DeleteCommitContentTest.php
  • tests/Unit/Actions/DeleteCommitTest.php
  • tests/Unit/Actions/DeleteUserTest.php
  • tests/Unit/Actions/UpdateCommitStatusesTest.php
  • tests/Unit/Jobs/GenerateHeadlineSummaryTest.php
  • tests/Unit/Jobs/GenerateHotHeadlineSummariesTest.php
  • tests/Unit/Services/HeadlineProviders/OpenAIHeadlineProviderTest.php

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The PR adds scheduled AI recaps for hot headlines. It selects eligible commit bodies from an eight-hour window, checks whether they support a recap, and stores results for up to two hours. Recaps are cleared when they expire or their source commits are removed or rejected, or their authors’ accounts are deleted.

Changes: Headline recap generation

Layer / File(s) Summary
Recap data and provider
database/migrations/2026_11_02_162353_add_summary_to_headlines_table.php, app/Models/Headline.php, app/Contracts/Services/HeadlineProvider.php, app/Queries/HeadlineSummaryCommitsQuery.php, app/Ai/Agents/HeadlineSummaryWriter.php, app/Services/HeadlineProviders/*, tests/Unit/Services/HeadlineProviders/OpenAIHeadlineProviderTest.php
Adds recap storage fields and a two-hour freshness value. Selects approved, top-level commits with nonempty bodies from the eight hours before the query cutoff, and limits sources to 100. The provider checks usefulness and validates recap output.
Scheduled generation and publication
app/Actions/CreateHeadlineSummary.php, app/Jobs/GenerateHeadlineSummary.php, app/Jobs/GenerateHotHeadlineSummaries.php, app/Exceptions/HeadlineSummaryFailed.php, routes/console.php, tests/Unit/Jobs/*, KNOWLEDGE_BASE.md
Schedules hot-headline processing every 15 minutes. Per-headline generation uses a cache lock and publishes only when the headline remains eligible and its source bodies have not changed.
Snapshot expiry and source invalidation
app/Actions/DeleteExpiredHeadlineSummaries.php, app/Actions/DeleteCommit.php, app/Actions/DeleteCommitContent.php, app/Actions/DeleteUser.php, app/Actions/UpdateCommitStatuses.php, tests/Unit/Actions/*
Clears expired recaps and clears linked recap fields when commits are deleted, detached, or rejected, or when an author account is deleted.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ConsoleScheduler
  participant GenerateHotHeadlineSummaries
  participant HotHeadlinesQuery
  participant GenerateHeadlineSummary
  participant CreateHeadlineSummary
  participant HeadlineSummaryCommitsQuery
  participant HeadlineProvider
  participant Database
  ConsoleScheduler->>GenerateHotHeadlineSummaries: Run every 15 minutes
  GenerateHotHeadlineSummaries->>HotHeadlinesQuery: Select hot headlines
  GenerateHotHeadlineSummaries->>GenerateHeadlineSummary: Dispatch a job for each eligible headline
  GenerateHeadlineSummary->>CreateHeadlineSummary: Generate recap for headlineId
  CreateHeadlineSummary->>HeadlineSummaryCommitsQuery: Get eligible commit bodies
  CreateHeadlineSummary->>HeadlineProvider: Summarize headline and bodies
  CreateHeadlineSummary->>Database: Store recap if source bodies still match
Loading

Possibly related PRs

Suggested reviewers: mnapoli, xheaven

Merge Risk: Minimal · up to f5e4c

The recap publication guard is working as intended, and no actionable issue remains before merge.

Security architecture risk: Low · up to f5e4c

The new recap lifecycle has meaningful safeguards: source eligibility is rechecked before storage, affected recaps are cleared during removal, and recaps remain outside current responses. No introduced security flaw was substantiated in the inspected backend. Future display rules and operational retention guarantees remain unresolved.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Eligible post authors can influence recap inputs through public text. The evidenced outcome is additional external processing and shared recap storage for the five hottest headlines, rather than a new privileged action or default public response field. The per-author selection and 100-source cap bound the sampled input, not the number of independently controlled accounts.

Trust Boundaries and Controls

  • observed — Post text crosses an external AI boundary as untrusted data. Instructions prohibit following embedded commands, opening links, identifying authors, and quoting posts. Before persistence, deterministic validation rejects blank, oversized, URL-bearing, handle-bearing, HTML-tag, and control-character output. These checks do not independently prove semantic grounding or anonymity.

Resilience and Maintainability Implications

  • inferred — The inspected deletion, content-erasure, account-erasure, and rejection paths clear recaps transactionally on the same headline rows used for publication. Together with publication-time source equality, this addresses either ordering of generation and those invalidations. Provider abstention writes nothing, and the job failure wrapper omits source bodies and the original exception message. These conclusions follow from source ordering, not exercised concurrent production execution.

Hardening Proposals

  • proposed — Before exposing recaps, define whether they represent a guest-public snapshot or must respect viewer-specific blocks. Establish consumer freshness rules and an operational cleanup expectation that remains explicit during queue outages and application rollback.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: generating short-lived recaps for hot headlines.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 24 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The source-erasure/publication guarantee still needs multi-connection PostgreSQL validation of the headline-row locking protocol, as current tests simulate interleavings on only one connection.

No open issues 🎉

What changed in this PR

Adds background recap generation to the existing headline system without exposing a new UI or endpoint. Recaps use recent public posts and are reused for up to two hours.

Changes:

  • Selects up to 100 authors’ strongest eligible posts from an eight-hour window for the five hottest topics.
  • Adds usefulness-gated AI generation, output validation, and source rechecks before publication.
  • Stores recap snapshots, clears expired or invalidated content, and adds focused tests.
File Description
tests/​Unit/​Services/​HeadlineProviders/​OpenAIHeadlineProviderTest.php Tests usefulness gating, abstention, writer configuration, and output validation.
tests/​Unit/​Jobs/​GenerateHotHeadlineSummariesTest.php Tests expiration cleanup and selective dispatch.
tests/​Unit/​Jobs/​GenerateHeadlineSummaryTest.php Tests source selection, reuse, publication guards, and sanitized failures.
tests/​Unit/​Actions/​UpdateCommitStatusesTest.php Tests recap invalidation after source rejection.
tests/​Unit/​Actions/​DeleteUserTest.php Verifies account deletion clears affected recaps.
tests/​Unit/​Actions/​DeleteCommitTest.php Verifies source deletion clears its recap.
tests/​Unit/​Actions/​DeleteCommitContentTest.php Verifies content erasure clears associated recaps.
routes/​console.php Schedules recap processing every fifteen minutes.
KNOWLEDGE_BASE.md Documents recap behavior and privacy boundaries.
database/​migrations/​2026_11_02_162353_add_summary_to_headlines_table.php Adds nullable recap text and creation timestamp.
app/​Services/​HeadlineProviders/​OpenAIHeadlineProvider.php Implements usefulness checks, generation, and response validation.
app/​Services/​HeadlineProviders/​NullHeadlineProvider.php Adds a no-op recap implementation.
app/​Queries/​HeadlineSummaryCommitsQuery.php Selects recent eligible sources with one contribution per author.
app/​Models/​Headline.php Adds recap casts, property annotations, and freshness duration.
app/​Jobs/​GenerateHotHeadlineSummaries.php Clears expired snapshots and dispatches eligible hot topics.
app/​Jobs/​GenerateHeadlineSummary.php Adds unique queued generation with retries and sanitized failures.
app/​Exceptions/​HeadlineSummaryFailed.php Reports failures without including provider response content.
app/​Contracts/​Services/​HeadlineProvider.php Defines the nullable recap provider contract.
app/​Ai/​Agents/​HeadlineSummaryWriter.php Defines constrained recap instructions and structured output.
app/​Actions/​UpdateCommitStatuses.php Clears associated recaps when sources are rejected.
app/​Actions/​DeleteUser.php Clears affected recaps during account erasure.
app/​Actions/​DeleteExpiredHeadlineSummaries.php Removes expired recap text and timestamps.
app/​Actions/​DeleteCommitContent.php Clears recaps before source content is erased.
app/​Actions/​DeleteCommit.php Clears recaps when source commits are deleted.
app/​Actions/​CreateHeadlineSummary.php Coordinates reuse, generation locks, and source-checked publication.

Review effort: Balanced

@paulinevos
paulinevos merged commit e557d66 into dev-null-sh:production Nov 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants