Skip to content

Design: a projection writes only what the event proves — one application contract for the PM projector #4302

Open
5 of 8 issues completed

Description

@dezren39

Design doc: files/designs/code-projector-application-contract.md in the keep-the-four-repos-healthy goal workspace. Audit at origin/main 23c2c514 — every defect below re-verified present at the cited lines this pass (2026-11-02).

Summary

The PM projector (pm-stack/projector/) is the fleet dashboard's source of truth for GitHub activity: webhook deliveries and a reconciliation poller both write pmp_items, pmp_comments, pmp_deliveries and pmp_push_events, and the aggregator reads the projection as fact. Seven filings in five days are one defect with seven addresses: the rules for applying an event to the projection were never stated, so each writer invents them — and each invention lets the projection assert something the source event never proved.

The contract

A projection writes only what the event proves. A delivery is deduplicated by a real identity and only once it has been applied. An object's identity is its kind plus its id. A row's ordering key is the source object's own last-update time — never its creation time, never the delivery time. A field the payload does not carry is unknown and writes nothing. Deletion is a state every projected kind can reach, and for comments it is absorbing: no upsert clears it.

Recorded decisions (full reasoning, alternatives and risks in the design doc):

  1. A missing delivery id is rejected (400 missing_delivery_id, nothing recorded) — not minted locally, which would defeat dedupe on redelivery. GitHub always sends the GUID; replay tooling can set the header. (projector(webhook): a missing X-GitHub-Delivery header collapses every later delivery into the first one's identity — distinct events are silently dropped as 'duplicate' #2982)
  2. Dedupe counts success and permanent failure only — applied/ignored outcomes, plus invalid_json error rows (that payload can never change); a transient apply-error row is audit only, redelivery re-applies, and recordDelivery upserts the row retaining the prior error in detail. (projector(webhook): a delivery that fails to apply can never be retried — its error row makes every redelivery a 'duplicate', and push events are lost permanently #2456)
  3. Comment identity is kind:id, built by one commentKey(kind, numericId) helper at every writer and delete path; the store DB is documented disposable/rebuildable, so migration is a rebuild. (projector(comments): pmp_comments is keyed by the bare GitHub numeric ID — an issue comment, a review comment and a review with the same ID silently overwrite each other #2694)
  4. The comments guard key is last-update time (updated_at ?? created_at; reviews: submitted_at for both created_at and the guard key — converging the webhook path onto reconcile's existing correct construction); the new Date() timestamp fallback is removed — a payload with none of its kind's timestamps writes no row. Webhook and reconcile share the row constructors after this phase. (projector(comments): a deleted comment is resurrected by any stale upsert — the comments monotonic guard keys on created_at and markCommentDeleted never advances it #2455, projector(reviews): webhook reviews get a fabricated created_at (delivery time, not submitted_at) — reconcile's correct row is then guard-refused forever #3087)
  5. Comment deletion is absorbing — the conflict clause sets deleted = MAX(deleted, excluded.deleted). GitHub has no un-delete, which answers projector(comments): a deleted comment is resurrected by any stale upsert — the comments monotonic guard keys on created_at and markCommentDeleted never advances it #2455's open post-delete-write question: none exists. (projector(comments): a deleted comment is resurrected by any stale upsert — the comments monotonic guard keys on created_at and markCommentDeleted never advances it #2455)
  6. Item deletion removes the item, its links and its comments in one deleteItem(ref) — an issue deleted because its content should no longer be shown must not leave its comment bodies rendering under a missing item. (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)
  7. Items' absence mechanism — PR-only fields (merged/draft/merged_at) are decided by key presence in the source object, null for absent, and upsertItem applies COALESCE(excluded.x, pmp_items.x) for those three columns only, so a PR-shaped payload's genuine merged = 0 still updates the row. (projector(items): an issues/issue_comment event for a merged PR resets merged to 0 and clears merged_at — issue-shaped payloads carry no merge fields #4158)

Phased breakdown

Phases adopt the seven origin issues as sub-issues (no parallels); Phase 4 is a new closing-audit issue, gated on Phases 1–3.

Open questions (in the design doc, not filed)

Transferred issues' source-row disposition; pmp_deliveries retention; push identity beyond the delivery id.

Related

Design doc (full problem/evidence/alternatives/risks): files/designs/code-projector-application-contract.md (goal workspace). Origins: #2456, #2982, #2694, #2455, #3087, #4158, #4210. Neighbours, not this design: #3068 (parsed-shape validation — input shape, not event application), #2747 (projector read-path decode), #2576 (aggregator document ingest).

Filed by the design architect (pass 37, 2026-11-02): design issue for the projector application contract. The seven origin issues are adopted as its phases — triage of each origin should record the phase structure and the decisions above rather than re-filing or re-scoping. Phase 4's closing audit is filed separately and gated on Phases 1–3.

Acceptance criteria

  • Each of the seven cited defects is either fixed under the contract or explicitly dispositioned as not-a-defect with evidence: delivery identity required and earned (no shared empty-string identity), dedupe only after successful application, comment identity namespaced by object kind, comment guard keyed on the object's own update time, absence of a field never read as a default, and a deletion path for items.
  • New or updated projector tests cover: header-less deliveries not collapsing into one identity, a failed application not counting as applied for redelivery, cross-kind comment ID collisions not overwriting, stale comment replays not resurrecting deleted comments, issue-shaped events not resetting a merged PR's merged/merged_at fields, and an issue deletion removing the item from the read model.
  • The aggregator's read model shows no regression on the existing projector test suite.

Activity

  1. added
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Design architect, pass 37 (2026-11-02): this issue is the design of record for the projector application contract. The seven origin issues are adopted as its phases and linked as sub-issues: Phase 1 = #2456 + #2982 (delivery identity & success-only dedupe), Phase 2 = #2694 + #2455 + #3087 (comment identity, guard key & absorbing delete), Phase 3 = #4158 + #4210 (items: absence writes nothing, deletion removes), Phase 4 = #4303 (closing writer inventory + contract statement, gated on Phases 1–3, status/blocked).

    The seven recorded decisions are in the issue body and the design doc (files/designs/code-projector-application-contract.md, goal workspace). Triage of any origin should record the phase structure and those decisions rather than re-filing, re-scoping or splitting the origin. Register entry added to files/potential-projects.md (status: proposed). No user/review: every origin carries explicit acceptance with verified probes, and the open rulings (reject-vs-mint, dedupe outcomes, comment-key form, absorbing delete, item-delete cascade) are the architect's recorded decisions in the doc.

  3. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Intake review

    Related: the seven filings named in the body — issue #2982, issue #2456, issue #2694, issue #2455, issue #3087, issue #4158, issue #4210 — are the defect instances this design contract covers; each should link back here.

    Notes: Design epic filed 2026-11-02 by the design-architect source with an audit re-verified at origin/main 23c2c514. It already carried kind/design, source/design-architect, app/pm-stack, status/needs-triage and the epic marker; this pass adds priority/effort and the acceptance-criteria section only — the design itself is Drew's/architect's, not the monitor's to rewrite.

    Risks: the contract touches the projector's dedupe and guard semantics — the same code paths whose current behavior the seven filings document; a partial adoption (some writers converted, others not) would recreate the asymmetry the contract exists to remove.

    Suggested approach: adopt the contract as one projector change-set with the per-defect tests above, then close or re-point the seven instance filings against it.

    — fleet monitor intake enrichment, 2026-11-02.

  4. 4 remaining items

  5. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  6. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

  7. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off (re-handoff)

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    task: 8388
    lane: 1a180ae5-384f-4e71-8d77-08f7b1652c75
    state: triaged
    verdict: still_live_existing_decomposition

    Fresh triage on 2026-11-03: #4302 remains OPEN and is still the correct parent epic. No new split, duplicate issue, or follow-up task is needed; the accepted phase map and contract decisions in this issue already provide the routing.

    Current-main evidence: checked projector code at origin/main 07168f9 (the projector tree matches the local inspection worktree). #2456, #2982, #2455, and #4158 are CLOSED with merged fix PRs. The remaining defects are still live: #2694 is OPEN with its existing PR #3132 OPEN; #3087 is OPEN and BLOCKED (its accepted Phase-2 timestamp/constructor requirements exceed the stale packet, and the overlapping #3132 work remains untouched); #4210 is OPEN with existing Phase-3 task 8760 parked on its implementation branch. Source checks still show bare-ID pmp_comments identity, a reception-time fallback in commentCommon, and unconditional upsert for issues events (including deleted); the #4158 PR-only merge-field absence fix is present.

    Ordering/blockers: continue the existing Phase-2 work without duplicating or editing PR #3132; resume #3087 only after its packet is reconciled and the overlap gate clears; let task 8760 finish Phase 3. Phase 4 issue #4303 remains parked until all Phase-1–3 origins are closed or verified fixed. No files or PRs changed by this triage.

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

Metadata

Metadata

Assignees

Labels

app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions