Repository navigation
feat: generate short-lived hot topic recaps - #1391
Conversation
|
No actionable comments were generated in the recent review.
|
| 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
Possibly related PRs
- dev-null-sh/dev-null.sh#1355: Adds headlines and commit-headline links used by recap generation.
- dev-null-sh/dev-null.sh#1389: Adds the hot-headline query used to select topics for recap generation.
- dev-null-sh/dev-null.sh#1361: Changes the commit author contract; recap source selection groups commits by
author_id.
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: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passedFull 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
There was a problem hiding this comment.
🔵 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
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
decideon 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.summaryandsummary_created_atfields. 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
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.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
Summary by CodeRabbit