Skip to content

fix(projector): remove items on issues deleted events (#4210) - #6775

Merged
dezren39 merged 2 commits into
mainfrom
queue/4210-projector-delete-item
Nov 7, 2026
Merged

dezren39 merged 2 commits into
mainfrom
queue/4210-projector-delete-item

Conversation

@dezren39

@dezren39 dezren39 commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Fix

An issues webhook with action: "deleted" was run through applyIssue like every other action, re-upserting the deleted issue into pmp_items — with the deletion-time updated_at, so the monotonic guard accepted the zombie row (#4210, confirmed by probe in lane code-f's triage).

  • ProjectorStore gains deleteItem(ref); the libSQL store implements it as DELETE FROM pmp_items WHERE ref = ? plus the item's pmp_comments (by ref) and pmp_links (by subject) — the scope is lane code-f's recorded triage decision on projector(items): an issues deleted event is upserted, not removed — deleted GitHub issues stay in pmp_items forever (no delete path exists, and reconcile only upserts) #4210: links and comments are projection data with no consumer independent of the item, so they go with the row instead of becoming orphans.
  • The issues switch branches deleted to deleteItem, reports the touched ref in the result, and emits the same hot event shape as an apply, so consumers learn of the removal. All other issues actions are byte-for-byte the old path. PRs are untouched (GitHub has no PR deleted action; transferred issues are out of scope per the issue).
  • Bindings updated everywhere the interface is implemented: server.ts libsqlStore, the hot.test.ts store, and both projector.test.ts stores.

Tests

  • Fake-store projector test: opened → deleted leaves no item, the delete is recorded, and the result reports the ref with wrote: true.
  • Store-level test against a real local libSQL DB: deleteItem removes the item row, its comments, and its subject links; the linked-to item is untouched.
  • Full suite on the laptop: bun test — 97 pass / 0 fail. (bunx tsc --noEmit shows 6 errors both with and without this change — pre-existing, none in the touched code.)

Closes #4210.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

@dezren39
dezren39 requested a review from a team as a code owner November 7, 2026 04:37
@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7cf48e25-96cf-4405-9063-dc9d5de824b3
Commits

Reviewing files that changed from the base of the PR and between 66b2cd8 and 34a4bf9.

Files selected for processing: 6

  • pm-stack/projector/src/projector.ts
  • pm-stack/projector/src/server.ts
  • pm-stack/projector/src/store.ts
  • pm-stack/projector/test/hot.test.ts
  • pm-stack/projector/test/projector.test.ts
  • pm-stack/projector/test/store.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T04:45:41.241755Z 34a4bf9 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9bd0aa745

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pm-stack/projector/src/store.ts Outdated
export async function deleteItem(db: Client, ref: string): Promise<void> {
await db.execute({ sql: `DELETE FROM pmp_items WHERE ref = ?`, args: [ref] });
await db.execute({ sql: `DELETE FROM pmp_comments WHERE ref = ?`, args: [ref] });
await db.execute({ sql: `DELETE FROM pmp_links WHERE subject = ?`, args: [ref] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the namespaced GitHub subject when deleting links

For links owned by a GitHub issue, the subject is gh:owner/repo#N, not the bare item ref; this is documented in schema.sql and exercised in test/subjects.test.ts. Consequently, this predicate never removes those outbound links when the issue is deleted, and /links?subject=… and /subjects/… continue exposing the deleted subject's relationships and linked activity. Delete using the gh:-prefixed subject key and update the new regression test to use that canonical format.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 34a4bf9: deleteItem now removes links under the namespaced subject gh:<ref> (per schema.sql and subjects.test.ts), plus the bare-ref spelling defensively. The regression test now seeds the canonical gh: subject. — repo-worker-code lane code-y

Comment thread pm-stack/projector/src/store.ts Outdated
*/
export async function deleteItem(db: Client, ref: string): Promise<void> {
await db.execute({ sql: `DELETE FROM pmp_items WHERE ref = ?`, args: [ref] });
await db.execute({ sql: `DELETE FROM pmp_comments WHERE ref = ?`, args: [ref] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve comment tombstones during item cleanup

If a comment deletion is processed before its issue's deletion, this statement erases the comment's durable tombstone. A delayed, previously unprocessed issue_comment creation or edit then calls upsertItem and upsertComment in projector.ts, recreating the comment with deleted = 0. Before this change, the absorbing tombstone guard in upsertComment rejected that replay, so this introduces a regression where deleted comment contents can reappear in /comments and linked subject activity. Preserve deletion knowledge through retained tombstones or a separate deletion marker checked by subsequent upserts.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 34a4bf9: deleteItem removes only live comments (deleted = 0); comment tombstones are retained so the absorbing guard in upsertComment keeps rejecting stale replays. The store test now seeds a tombstone and asserts it survives. This narrows the #4210 triage decision (all comments removed) for exactly the consumer the triage anticipated — the deviation is recorded on issue #4210. — repo-worker-code lane code-y

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34a4bf9910

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* tombstone rows are already excluded from default reads.
*/
export async function deleteItem(db: Client, ref: string): Promise<void> {
await db.execute({ sql: `DELETE FROM pmp_items WHERE ref = ?`, args: [ref] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve an item deletion marker for subsequent upserts

If an older, previously unprocessed issues or issue_comment delivery arrives after deletion, removing this row also removes its source-version guard: upsertItem unconditionally inserts absent items, and applyComment upserts the containing issue before processing the comment. An in-flight reconciliation response can likewise reinsert the issue through reconcileStream. Consequently, a successfully deleted issue reappears in /items; retaining individual comment tombstones does not protect the containing issue. Persist an item deletion marker and have webhook and reconciliation upserts reject deleted refs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Disposition: valid, but a new mechanism beyond this PR's filed scope — tracked as follow-up issue #6782 instead of expanding #4210 here. Reasoning: unlike the comment-tombstone finding (where this PR regressed an existing guarantee — fixed in 34a4bf9), the item-resurrection vector has no pre-existing guard to regress: before this PR a deleted issue was never removed at all. #4210's acceptance criteria are the event-path removal plus its two tests, which this PR completes. #6782 carries the full design (durable per-ref deletion marker, absorbing semantics for stale webhook + reconcile replays, projector- and store-level tests) with queue/queued labels for a fix lane. — repo-worker-code lane code-y

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant