P7: thread backfill reads conversation containers and asks with If-None-Match
Build / Build (push) Failing after 7m52s

The FetchReplies job reads a thread's FEP-171b contextHistory before its FEP-7888 context: the posts each Add (or
Forte's plain Create) brought in, read from their own servers. A thread collection read whole keeps its ETag when the
document itself changes with every post (it counts them, or holds them all with no further page); the next read sends
it as If-None-Match and a 304 ends the job. The ETag is kept as sent, since NodeBB's has no quotes and the typed header
drops it. A context naming a post we hold (Forte's first post) is not fetched.

Checked in the pasture: Mastodon 4.7.3 answers the second read 304; NodeBB 4.16's unquoted ETag is kept (23/23 in its
scenario); a Forte thread completes through its replies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-07 21:33:49 +02:00
1 parent fb4949b511
commit 62189bf11d
10 files changed
+268 -35

No files matched your search

+13 -2
View File
@@ -96,7 +96,9 @@ Priorities, used throughout:
- `Move`.
- `QuoteRequest`, its `Accept`/`Reject` (with `result`), and `Delete{QuoteAuthorization}`.
- `FeatureRequest`/`FeatureAuthorization` (4.6).
- **Threads:** `context` is a dereferenceable collection of the thread (FEP-7888, threads started on 4.5+).
- **Threads:** `context` is a dereferenceable collection of the thread (FEP-7888, threads started on 4.5+). It
answers `If-None-Match` with a 304 (a weak ETag over the body, 4.7.3), but the body embeds only the first page, so its
ETag stands for the thread only while the thread fits on that page.
- **Interaction counts:** `likes` and `shares` carry `totalItems`.
- **Quotes:** `quote`, plus `quoteUri` and `_misskey_quote`, `quoteAuthorization`, and `interactionPolicy.canQuote`.
- **Link previews:** from 4.7, a `{type: Link, href}` **attachment** names the link a card is made from (FEP-8967).
@@ -659,7 +661,9 @@ What it showed:
- **NodeBB:**
- A topic's first post is an `Article` with `name`, **`summary` = an excerpt** and `preview`; replies are Notes.
- Categories are `Group`s **without `followers`**.
- `context` is a paged collection with an **ETag digest**; NodeBB refetches with `If-None-Match`.
- `context` is a collection with an **ETag digest** of its posts, sent **without quotes** (not an RFC 9110
entity-tag: .NET's typed header drops it, so PrivaPub keeps the raw value); it carries `totalItems` and its items
inline, and answers `If-None-Match` with a 304. NodeBB refetches with it too.
- Since 4.15, an Announce of anything but a Create or a plain object is accepted only from Group actors.
- It sends `Move`/`Remove` of a whole context (FEP-f15d) and `Add{post → context}` (FEP-11dd).
- Chats are private Notes, threaded by `inReplyTo` into the room they answer.
@@ -855,6 +859,8 @@ without a port, before it gives out its OAuth client.
- Its Mastodon API resolves an account elsewhere only when the search is not limited to a type
(`/api/v2/search?resolve=true`, no `type=accounts`); reactions go through Pleroma's route
(`PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`).
- **Threads:** a post's `context` is `/collections/conversations/<id>`, a collection whose first page lists the
thread's post ids (FEP-7888), with no ETag; no `contextHistory`.
- **Pasture evidence (2026-10-05, `tools/pasture/scenarios/mitra.sh`):** 28 checks pass, with no change to PrivaPub:
follows both ways, posts, replies both ways, likes and reposts both ways, an emoji reaction, a poll and alice's vote,
edits and deletions both ways, direct messages both ways, the unfollow, statistics.
@@ -918,6 +924,11 @@ without a port, before it gives out its OAuth client.
- **Everything in a thread is added to its conversation** (FEP-171b): its edits come only as `Add{Update}`, under the
same activity id as the post's `Create`, and likes as `Add{Like}`. PrivaPub unwraps Create, Update and Delete, telling
an Update from the Create whose id it reuses by what it carries.
- **Its conversation container is not named by its posts:** `contextHistory` (and `context`) pointing at
`…/conversation/<uuid>` ride on the activities only; a Note's `context` is the thread's first post and it has no
`contextHistory`, though the FEP asks the first post for one. The container (public threads: unsigned GETs work)
lists the first post's plain `Create`, then an `Add{Create}` for every post. A post's `replies` lists every comment of
the thread, embedded, and is what PrivaPub reads to complete a Forte thread (checked live 2026-10-07).
- **Its own collections name themselves wrongly:** a channel's followers, following and outbox give an `id` with the
gateway path twice and a trailing `?`, which 404s, so their counts are not read.
- **Running it:** no image is published; `images/forte` builds its tag with composer on Hubzilla's PHP image, behind an
+5 -2
View File
@@ -677,8 +677,11 @@ it, raw where it doesn't.
#### P7 Threads, communities and the social graph
- **Thread backfill:**
- read in order: `contextHistory`, then `context` (paged, with ETag), then `replies`. **Done (2026-10-05), but for
`contextHistory` and ETags:** a persona opening a public remote thread queues `FetchReplies` for the post and its
- read in order: `contextHistory`, then `context` (paged, with ETag), then `replies`. **Done (2026-10-05; `contextHistory`
and ETags 2026-10-07: a container's `Add`ed Creates and Updates are read for their posts; a whole read of a collection
that counts its posts or holds them all keeps its ETag, sent back as `If-None-Match`, a 304 checked live on Mastodon
4.7.3 and NodeBB 4.16, whose ETag has no quotes; neither Forte nor Mitra puts `contextHistory` on a post, and Forte's
`replies` already holds its whole thread):** a persona opening a public remote thread queues `FetchReplies` for the post and its
root, at most hourly each. The job reads the thread's FEP-7888 `context` collection (posts or, as FEP-f228 allows,
the activities that made them), else the post's `replies` (PeerTube's `comments`) and theirs, two levels down. It
reads at most 5 pages and 100 posts per job, signed by the instance actor, and stores what it lacks through