Skip to content

Harden site_generator page generation against malformed data (#74) - #76

Merged
lmorchard merged 2 commits into
mainfrom
worktree-harden-site-generation
Jul 25, 2026
Merged

lmorchard merged 2 commits into
mainfrom
worktree-harden-site-generation

Conversation

@lmorchard

Copy link
Copy Markdown
Owner

Closes #74 (follow-up from #73's Copilot review).

Problem

generate_activities_pages ran par_iter().for_each(|d| generate_activity_page(...).unwrap()), and generate_activity_page unwrapped both the activity's actor id and the actor-map lookup. So a malformed or incomplete import — a public activity whose actor is missing or un-parseable — panicked mid-build instead of surfacing an error. The actors map comes from get_actors_by_id, which already skips un-parseable actor rows, so the DB can legitimately lack an actor an activity references.

Fix

  • generate_activities_pages uses par_iter().try_for_each(...), so a genuine render/IO error propagates as Err instead of panicking a rayon worker.
  • generate_activity_page filters public activities first, then skips (with a warn!) any whose actor id is absent or whose actor isn't in the map — mirroring the resilience already in db::actors::get_actors. A missing actor degrades one activity, not the whole build.
  • No .unwrap() remains on the actor-resolution path.

Verification

Regression test generate_activities_pages_skips_activity_with_missing_actor: a day containing an activity whose actor is absent renders without panic (activity skipped). Confirmed red before / green after the fix. make check + make test green (44 lib tests).

Behavior-preserving for well-formed data.

Spec/plan: docs/dev-sessions/2026-07-24-1732-harden-site-generation/.

🤖 Generated with Claude Code

generate_activities_pages ran par_iter().for_each(|d| ...unwrap()), and
generate_activity_page unwrapped both the activity's actor id and the actor
lookup — so a malformed/incomplete import (a public activity whose actor is
missing or un-parseable) panicked mid-build.

- generate_activities_pages now uses par_iter().try_for_each(...) so a genuine
  render/IO error propagates as Err instead of panicking a rayon worker.
- generate_activity_page filters public activities first, then skips (with a
  warn!) any whose actor id is absent or whose actor isn't in the map, mirroring
  the resilience of db::actors::get_actors. A missing actor degrades one
  activity, not the whole build.

Add a regression test: a day with an activity whose actor is absent renders
without panic (the activity is skipped).

Closes #74

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Hardens the site generator’s per-day page rendering so malformed activity data (specifically unresolved actors) no longer panics mid-build, aligning runtime behavior with the existing “skip bad rows” approach already used in actor loading.

Changes:

  • Switch per-day parallel generation from for_each(...).unwrap() to try_for_each(...) so render/IO failures propagate as Result instead of panicking rayon workers.
  • In generate_activity_page, replace actor-resolution unwrap()s with skip-with-warn! when an activity’s actor id is missing or the actor is absent from the provided map.
  • Add a regression test proving orphaned-actor activities are skipped without aborting page generation.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/site_generator.rs Removes panic paths in parallel generation and actor resolution; adds regression test for missing-actor resilience.
docs/dev-sessions/2026-07-24-1732-harden-site-generation/spec.md Documents the robustness issue and desired behavior for #74.
docs/dev-sessions/2026-07-24-1732-harden-site-generation/plan.md Captures the implementation plan and verification steps for the fix.
docs/dev-sessions/2026-07-24-1732-harden-site-generation/notes.md Empty session notes placeholder for the work.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/site_generator.rs Outdated
Comment on lines +147 to +151
let items: Vec<activitystreams::Activity> = db_activities
.get_activities_for_day(day)?
.iter()
.map(|activity| {
let actor_id: &String = activity.actor.id().unwrap();
let actor: &activitystreams::Actor = actors.get(actor_id).unwrap();
(activity, actor)
})
.filter(|(activity, _actor)| {
// todo: any actor-related filtering needed here?
activity.is_public()
})
.map(|(activity, actor)| {
.filter(|activity| activity.is_public())
.filter_map(|activity| {
…_page

get_activities_for_day returns an owned Vec<Activity>, so iterate by value
(into_iter) and mutate the owned activity in place instead of .iter() + clone,
dropping one allocation/copy per rendered activity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmorchard
lmorchard merged commit 16ad68f into main Jul 25, 2026
3 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.

site_generator page generation panics on malformed data instead of surfacing errors

2 participants