Skip to content

Backfill missing published/updated on inbox & outbox reads#3320

Open
pfefferle wants to merge 8 commits into
trunkfrom
fix/missing-activity-published-fallback
Open

Backfill missing published/updated on inbox & outbox reads#3320
pfefferle wants to merge 8 commits into
trunkfrom
fix/missing-activity-published-fallback

Conversation

@pfefferle
Copy link
Copy Markdown
Member

Fixes #

Proposed changes:

  • Outbox::get_activity() now backfills a missing top-level published from the outbox row's post_date_gmt, and a missing updated from post_modified_gmt whenever the row was modified after creation (the existing Update-type carve-out is preserved). Also rejects the 0000-00-00 00:00:00 sentinel that pending rows carry so the fallback can't synthesize a 1970-01-01 date.
  • New Inbox::get_activity() helper mirrors the outbox method: decodes post_content, validates the result, hydrates an Activity, applies the same date fallback. Returns WP_Error on missing/wrong-type/corrupt rows.
  • The C2S inbox listing endpoint (Actors_Inbox_Controller::prepare_item_for_response) now goes through Inbox::get_activity() and is shaped exactly like its outbox counterpart — passes $item->ID, returns WP_Error on failure, the get_items() loop skips errored items, and per-item serialization uses to_array( false ) (drops the per-item @context since the collection carries it, and strips bto/bcc).
  • The previous to_array( true, true ) was leaking the stored blind audience (bto/bcc) on the C2S inbox endpoint; this change closes that as a side effect of unifying with the outbox style.

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • Make sure your site is federating (you have at least one follower or are otherwise able to inspect the outbox via REST).
  • Verify outbox enrichment:
    • Hit GET /wp-json/activitypub/1.0/actors/{id}/outbox with auth (or browse a public outbox).
    • Every item in orderedItems should now carry a published field; items that were modified after they were queued should also carry an updated.
    • If you have an existing outbox row whose stored JSON happened to be missing published, it now shows up — no DB migration needed, the value is computed at read time.
  • Verify inbox enrichment:
    • Receive (or simulate receiving) an activity that the remote sent without a published field.
    • Hit the C2S inbox listing endpoint for the recipient user.
    • The returned item carries published derived from the row's post_date_gmt.
  • Verify the blind-audience strip:
    • Inspect any item in the C2S inbox listing — neither bto nor bcc should appear, even though they're persisted internally for addressing.
  • Run the test suite: npm run env-test (2565 tests, all passing locally).

Changelog entry

A changelog file is already included in this branch at .github/changelog/fix-c2s-activity-date-fallback. No automation needed.

pfefferle added 7 commits May 19, 2026 11:29
Outbox::get_activity already had the row in hand but only set updated,
and only for Update activities. Generalize: when published is empty,
fill it from post_date_gmt; when updated is empty AND (type is Update
OR the row was modified after creation), fill from post_modified_gmt.

The existing line also read post_modified (local TZ); switch to
post_modified_gmt so the value is correct on non-UTC sites. Tests cover
both fill paths plus the "no synthesis when unmodified" case.
…970 dates

Outbox::add inserts with post_status = 'pending', so WordPress leaves
post_date_gmt as '0000-00-00 00:00:00'. The previous ! empty() guard
treated that string as truthy and would have set published to
'1970-01-01T00:00:00Z' on every still-pending row -- worse than leaving
the field empty.

Reject the sentinel explicitly for both post_date_gmt and
post_modified_gmt. Tests now populate the date columns to real
timestamps so they verify meaningful behavior instead of round-tripping
epoch zero, and a new test pins the zero-sentinel case to "stay empty".
Mirrors Outbox::get_activity: decodes the stored JSON, returns an
Activity object, and falls back to the inbox row's post_date_gmt /
post_modified_gmt when the activity itself is missing published or
updated. Rejects the '0000-00-00 00:00:00' sentinel for both columns so
the fallback can't synthesize a 1970-01-01 date when a pending row's
GMT timestamps haven't been populated yet.
The actors inbox controller's prepare_item_for_response used to return
the raw json_decoded post_content, which meant the new published/updated
fallback in Inbox::get_activity bypassed it. C2S clients reading the
inbox endpoint now see the enriched dates instead of empty fields when
the remote sender omitted them.
prepare_item_for_response now passes $item->ID to Inbox::get_activity,
returns the WP_Error on failure (instead of swallowing it as array()),
and the get_items loop skips WP_Error items the same way the outbox
loop does. Also drops the per-item JSON-LD context and blind audience
(bto/bcc) on the wire by calling to_array(false), matching the outbox
controller's serialization. Inbox and Outbox collections themselves
are unchanged.
Copilot AI review requested due to automatic review settings May 19, 2026 10:03
@pfefferle pfefferle self-assigned this May 19, 2026
@pfefferle pfefferle requested a review from a team May 19, 2026 10:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backfills missing published/updated timestamps on activities served from the inbox and outbox C2S endpoints by falling back to the underlying CPT row's GMT timestamps, while introducing a new Inbox::get_activity() helper that mirrors the outbox shape and incidentally stops leaking the persisted blind audience (bto/bcc) on the inbox listing.

Changes:

  • Outbox::get_activity() reworked to fall back to post_date_gmt/post_modified_gmt (rejecting the 0000-00-00 00:00:00 sentinel) and preserve the Update carve-out.
  • New Inbox::get_activity() helper hydrates post_content into an Activity, validates, and applies the same fallback; Actors_Inbox_Controller::prepare_item_for_response now routes through it and returns WP_Error on failure, with to_array( false ) stripping bto/bcc.
  • Test coverage added for outbox/inbox fallback semantics, sentinel rejection, type/JSON validation, and the existing-published preservation case.

Reviewed changes

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

Show a summary per file
File Description
includes/collection/class-outbox.php Adds date fallback from row GMT timestamps with sentinel rejection.
includes/collection/class-inbox.php New get_activity() mirroring outbox semantics with WP_Error handling.
includes/rest/class-actors-inbox-controller.php Routes C2S inbox listing through Inbox::get_activity(), skipping errors and using to_array(false) to drop blind audience.
tests/phpunit/tests/includes/collection/class-test-outbox.php New tests for published/updated fallbacks, sentinel handling, and modification-time logic.
tests/phpunit/tests/includes/collection/class-test-inbox.php New tests for the inbox helper including error cases.
.github/changelog/fix-c2s-activity-date-fallback Changelog entry.

Comment thread includes/collection/class-outbox.php Outdated
Switching post_modified to post_modified_gmt fixed the non-UTC timezone
bug but introduced a regression for Update activities at federation
time: the Dispatcher reads outbox rows while they are still pending,
and pending rows leave _gmt columns as the 0000-00-00 sentinel while
the local columns are populated via current_time('mysql'). The sentinel
rejection then short-circuited the Update carve-out, so Update
activities started federating without an updated field.

Fall back to get_gmt_from_date( $post->post_modified ) when the GMT
column is the sentinel but the local column is set. Apply the same
derivation to post_date_gmt so pending Create activities also gain a
published field. A regression test pins the pending-row state the
Dispatcher actually sees.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants