fix: kind 1619 was in the lowercase-e engagement filter, where it matched nothing

NIP-34's PR Update example carries only `["E", <pull-request-event-id>]` —
there is no lowercase `e` tag on a 1619 at all, and
GitPullRequestUpdateEvent.build() writes only RootEventTag to match. Listing
1619 in RepliesAndReactionsKinds2 (the `#e` filter's kind list) therefore
never pulled a single PR revision, and the comment claiming it was "rooted at
the target patch/PR/issue via a `root`-marked `e` tag" was wrong for that kind
(it is correct for the 1630-1633 statuses beside it).

The `#E` filter added in the previous commit is what actually makes a PR's
revision chain reachable from an on-screen PR row, so 1619 moves there and
comes out of the `e` list.

Nip34NotificationCoverageTest asserted the false half, which is how this
survived: it now checks each kind against the filter that can actually match
it, and pins 1619 out of the `e` list so it cannot drift back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARAmQQWbT2gGVo9tysdAgM
This commit is contained in:
Claude
2026-09-12 01:44:32 +00:00
parent 2616bbfea8
commit 92f3fd49b0
2 changed files with 48 additions and 17 deletions
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal
import com.vitorpamplona.amethyst.commons.relayClient.account.nip01Notifications.NotificationsPerKeyKinds2
import com.vitorpamplona.amethyst.commons.relayClient.event.watchers.RepliesAndReactionsKinds2
import com.vitorpamplona.amethyst.commons.relayClient.event.watchers.RootScopedRepliesKinds
import com.vitorpamplona.amethyst.service.notifications.NotificationDispatcher
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
@@ -32,6 +33,7 @@ import com.vitorpamplona.quartz.nip34Git.status.GitStatusAppliedEvent
import com.vitorpamplona.quartz.nip34Git.status.GitStatusClosedEvent
import com.vitorpamplona.quartz.nip34Git.status.GitStatusDraftEvent
import com.vitorpamplona.quartz.nip34Git.status.GitStatusOpenEvent
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -116,25 +118,30 @@ class Nip34NotificationCoverageTest {
/**
* A status/update event's discovery path when a repo or PR is on screen: the
* [`e`=<targetId>][RepliesAndReactionsKinds2] engagement subscription. Without
* this the closed/merged pill on a repo listing can never populate the
* status event's only other route to the device is the `#p`=me subscription,
* which only fires for accounts that were pre-tagged as participants.
* Patches/issues/PRs are self-anchored (they ARE the target, not events
* about the target), so they are intentionally NOT expected here.
* engagement subscription. Without this the closed/merged pill on a repo
* listing can never populate — the status event's only other route to the
* device is the `#p`=me subscription, which only fires for accounts that were
* pre-tagged as participants. Patches/issues/PRs are self-anchored (they ARE
* the target, not events about the target), so they are intentionally NOT
* expected here.
*
* Which of the two engagement filters carries a kind depends on how that kind
* anchors itself, so the two halves are asserted separately below: statuses
* and replies use a `root`-marked lowercase `e`, PR revisions use NIP-22's
* uppercase `E`. Asserting the wrong half passes the kind list while matching
* nothing on the wire.
*/
@Test
fun `status and PR-update kinds are pulled by the engagement subscription`() {
val threadedActivityKinds =
fun `status kinds are pulled by the lowercase-e engagement subscription`() {
val eAnchoredActivityKinds =
setOf(
GitPullRequestUpdateEvent.KIND,
GitReplyEvent.KIND,
GitStatusOpenEvent.KIND,
GitStatusAppliedEvent.KIND,
GitStatusClosedEvent.KIND,
GitStatusDraftEvent.KIND,
)
val missing = threadedActivityKinds - RepliesAndReactionsKinds2.toSet()
val missing = eAnchoredActivityKinds - RepliesAndReactionsKinds2.toSet()
assertTrue(
"Kinds $missing are missing from RepliesAndReactionsKinds2. When a repo/PR row is " +
"on screen the app fetches replies + reactions targeting the visible events — " +
@@ -144,4 +151,26 @@ class Nip34NotificationCoverageTest {
missing.isEmpty(),
)
}
/**
* Kind 1619 has no lowercase `e` tag at all — NIP-34's PR Update example carries
* only `["E", <pull-request-event-id>]` — so it can only ever arrive through the
* uppercase root-scope filter. It sat in [RepliesAndReactionsKinds2] for a while
* and matched nothing there.
*/
@Test
fun `PR-update kind is pulled by the uppercase-E engagement subscription`() {
assertTrue(
"GitPullRequestUpdateEvent (1619) is missing from RootScopedRepliesKinds. It anchors " +
"at the PR it revises with NIP-22's uppercase `E` and carries no lowercase `e`, " +
"so the `#e` engagement filter can never surface a PR's revision chain.",
GitPullRequestUpdateEvent.KIND in RootScopedRepliesKinds,
)
assertFalse(
"GitPullRequestUpdateEvent (1619) is back in RepliesAndReactionsKinds2, the `#e` " +
"filter's kind list. 1619 has no lowercase `e` tag, so the entry matches nothing " +
"and only makes the filter look like it covers PR updates.",
GitPullRequestUpdateEvent.KIND in RepliesAndReactionsKinds2,
)
}
}
@@ -81,7 +81,9 @@ val RepliesAndReactionsKinds =
val RootScopedRepliesKinds =
listOf(
CommentEvent.KIND,
// NIP-34 PR revisions point at the PR they revise through `E`.
// NIP-34 kind 1619 carries the PR it revises in `E` and has no lowercase
// `e` at all (see the spec's PR Update example), so this filter is the
// only engagement route to a PR's revision chain.
GitPullRequestUpdateEvent.KIND,
)
@@ -92,12 +94,12 @@ val RepliesAndReactionsKinds2 =
NIP90StatusEvent.KIND,
TorrentCommentEvent.KIND,
GitReplyEvent.KIND,
// NIP-34 PR revision (1619) and status events (1630/1631/1632/1633).
// Rooted at the target patch/PR/issue via a `root`-marked `e` tag, so
// an `e=<targetId>` engagement fetch surfaces the PR's revision chain
// and every open/applied/closed/draft transition — the signal
// GitStatusIndex needs to answer isClosedOrResolved() for repo rows.
GitPullRequestUpdateEvent.KIND,
// NIP-34 status events (1630/1631/1632/1633). Rooted at the target
// patch/PR/issue via a `root`-marked `e` tag, so an `e=<targetId>`
// engagement fetch surfaces every open/applied/closed/draft transition —
// the signal GitStatusIndex needs to answer isClosedOrResolved() for
// repo rows. PR revisions (1619) anchor with `E` instead and are pulled
// by [RootScopedRepliesKinds]; listing them here matched nothing.
GitStatusOpenEvent.KIND,
GitStatusAppliedEvent.KIND,
GitStatusClosedEvent.KIND,