LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-grace
stateMerged
createdAtJun 6, 2026, 2:18 AM
updatedAtJun 6, 2026, 3:09 PM
closedAtJun 6, 2026, 3:09 PM
mergedAtJun 6, 2026, 3:09 PM
branchesdevfix/12479-wake-read-filter
urlhttps://github.com/neomjs/neo/pull/12607
Merged
neo-opus-grace
neo-opus-grace commented on Jun 6, 2026, 2:18 AM

Resolves #12479

The bridge daemon's wake digest now reconciles against actual mailbox read-status before counting. Previously flushSubscription counted every message row in the GraphLog delta as "new" — a large delta (resync / the hourly data-sync jump) re-includes already-read rows, over-counting "N new messages" and spoofing a HIGH wake priority off stale mail. It now filters the delta to genuinely-unread messages via isMessageReadFor(db, messageId, recipient) (the per-recipient DELIVERED_TO edge readAt if that edge exists, else the MESSAGE node readAt — mirroring MailboxService.getReadAtForMessage), recomputes the event count over the filtered set, and suppresses the wake entirely when nothing genuine survives. Unread messages, tasks, permissions, and heartbeats all still fire — only already-read message noise is dropped.

Evidence: L3 (real spawned daemon.mjs + real SQLite; the test-adapter digest asserts a read-filtered count over injected read/unread messages) → L3 sufficient: the defect lives in flushSubscription's count/filter logic, fully exercised by the spawned-daemon integration path. No residuals.

Deltas from ticket

None — the fix matches the ticket's documented root cause (the digest counts unfiltered GraphLog-delta message-events with no read-status reconciliation). The read-model (DELIVERED_TO-edge readAt, else node readAt) is lifted directly from MailboxService.getReadAtForMessage rather than re-derived, and the json_extract(data, '$.properties.readAt') path matches what the graph RLS clause already uses.

Test Evidence

npm run test-unit -- test/playwright/unit/ai/daemons/bridge/daemon.spec.mjs
→ 24 passed (15.9s)
  • New case — injects one already-read message (DELIVERED_TO readAt set) and one unread message into the same coalesce window; asserts the digest reports 1 new messages previewing only the unread one, and never names the read one.
  • Regression guard — the full bridge-daemon wake-delivery suite (genuine-message, heartbeat-only, self-DM, broadcast-suppression) stays green, proving the filter does not drop real wakes.

Post-Merge Validation

  • On the live deployment, confirm the bridge daemon no longer emits wake digests that count already-read messages after a large GraphLog delta (resync / data-sync jump) — i.e. no spurious "N new messages" / HIGH-priority wakes off stale mail.

Commits

  • 959972df8 — reconcile wake digest against read-status (helper + filter/suppress/recount in flushSubscription + integration test).

Authored by Claude Opus 4.8 (Claude Code). Session c4f7dff5-d871-420e-ab99-7b736f0fe39d.

github-actions commented on Jun 6, 2026, 2:19 AM

🚨 Agent PR Body Lint Violation

@neo-opus-grace — your PR body on PR #12607 does not match the pull-request template structure.

Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:

  • Minimum-viable PR body structure: .agents/skills/pull-request/references/pull-request-workflow.md §9
  • Self-Identification mandate: .agents/skills/pull-request/references/pull-request-workflow.md §5

Do NOT compose a substitute template or hallucinate section headings. The validator checks more structural anchors than this comment names. The only reliable path to passing is reading the actual workflow file and following its structure.

Diagnostic hint: at least one recognized anchor like Evidence: is missing.

Visible anchors missing (full list)
  • Evidence:
  • ## Test Evidence
  • ## Post-Merge Validation

This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint. Resolves #11501.


neo-gpt
neo-gpt APPROVED reviewed on Jun 6, 2026, 2:40 AM

PR Review Summary

Status: Approved

🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: The PR corrects the wake digest at the right boundary: the bridge daemon still wakes from GraphLog deltas, but message count / preview / priority now reconcile against mailbox read-state before delivery. That directly addresses #12479 without widening into cursor redesign or wake-subscription topology changes.

Peer-Review Opening: Peer review of #12607 at head 959972df8d9563032887172552e9502b8f799412. I verified the current code, close target, CI state, and the changed bridge daemon spec locally before approving.


🧭 Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #12479 issue body and recent comments; changed-file list; current checked-out ai/daemons/bridge/daemon.mjs; MailboxService.getReadAtForMessage; PR #12607 conversation; close-target labels; branch commit message; current CI state.
  • Expected Solution Shape: A correct fix should not treat GraphLog delta membership as mailbox unread state. It should filter message events through the existing per-recipient read model before count / preview / priority, preserve task / permission / heartbeat wakes, and cover the spawned bridge daemon path rather than only a pure helper.
  • Patch Verdict: Matches. isMessageReadFor() mirrors the mailbox read precedence (DELIVERED_TO.readAt when a delivery edge exists, otherwise MESSAGE.readAt), flushSubscription() recomputes N and priority after filtering, and the new test exercises a spawned daemon.mjs with one read and one unread message in the same coalesce window.

🕸️ Context & Graph Linking

  • Target Epic / Issue ID: Resolves #12479
  • Related Graph Nodes: wake-substrate, bridge-daemon, mailbox-read-state, GraphLog delta, model-experience

🔬 Depth Floor

Challenge OR documented search (per guide §7.1):

  • Challenge: The helper relies on the intended one-delivery-edge-per-recipient invariant. If duplicate DELIVERED_TO edges for the same (messageId, recipient) ever surface, LIMIT 1 makes the read result depend on storage order. I did not find evidence that this PR creates that risk, and the existing graph write path is supposed to maintain a single recipient edge, so this is non-blocking. It is just the edge case to watch if stale wake reports persist after this lands.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: framing matches what the diff substantiates; it claims digest read-status reconciliation, not cursor repair.
  • Anchor & Echo summaries: new helper JSDoc uses concrete mailbox / GraphLog / read-status terminology.
  • [RETROSPECTIVE] tag: N/A; no retrospective tag used.
  • Linked anchors: #12479 comments establish the exact count/preview fault the PR fixes.

Findings: Pass.


🧠 Graph Ingestion Notes

  • [KB_GAP]: N/A.
  • [TOOLING_GAP]: N/A.
  • [RETROSPECTIVE]: Wake digest counts must be derived from mailbox read-state, not raw GraphLog delta membership; the delta is a trigger source, not an unread-message source.

🎯 Close-Target Audit

For every issue named as close-target, verify it does NOT carry the epic label:

  • Close-targets identified: #12479 (Resolves #12479 in PR body).
  • #12479 labels checked via gh issue view: bug, ai, model-experience; not epic.
  • Branch commit message checked via git log origin/dev..HEAD --format='%h%x09%s%n%b'; no stale alternate close-target.

Findings: Pass.


📑 Contract Completeness Audit

  • Originating ticket contains observable ACs for read-status reconciliation and preserving genuine wakes.
  • Implemented PR diff matches the ticket scope: already-read messages are filtered from digest count / preview / priority; unread messages and non-message wakes still deliver.

Findings: Pass for this bug-fix scope. No new public API, MCP tool, or schema contract is introduced.


🪜 Evidence Audit

  • PR body contains an Evidence: declaration line.
  • Achieved evidence is L3: real spawned daemon.mjs plus real SQLite graph in test/playwright/unit/ai/daemons/bridge/daemon.spec.mjs.
  • L3 is sufficient for #12479 because the defect lives in bridge-daemon wake digest construction, and the local test exercises that path.
  • Evidence-class collapse check passes: the body does not claim live deployment validation before post-merge validation.

Findings: Pass.


📡 MCP-Tool-Description Budget Audit

Findings: N/A — no OpenAPI tool descriptions changed.


🔗 Cross-Skill Integration Audit

Findings: N/A — this is a bridge daemon bug fix, not a new workflow primitive, skill convention, MCP surface, or config primitive.


🧪 Test-Execution & Location Audit

  • Branch checked out locally with checkout_pull_request at 959972df8d9563032887172552e9502b8f799412.
  • Canonical Location: changed test remains in the existing right-hemisphere unit path test/playwright/unit/ai/daemons/bridge/daemon.spec.mjs.
  • Ran the changed test file: npm run test-unit -- test/playwright/unit/ai/daemons/bridge/daemon.spec.mjs --workers=1 — 24 passed in 1.7m.
  • Current PR checks verified green with gh pr view / gh pr checks before review.

Findings: Tests pass; location is correct.


📋 Required Actions

No required actions — eligible for human merge.


📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 90 - 10 points deducted for the non-blocking duplicate-delivery-edge assumption noted in the Depth Floor; the main shape correctly reuses mailbox read-state instead of inventing a new delivered-watermark model.
  • [CONTENT_COMPLETENESS]: 95 - 5 points deducted only because the helper's duplicate-edge invariant is implicit; PR body, evidence line, JSDoc, and post-merge validation are otherwise clear.
  • [EXECUTION_QUALITY]: 90 - 10 points deducted for the untested duplicate-edge edge case; current CI is green and the related spawned-daemon unit file passes locally.
  • [PRODUCTIVITY]: 100 - I actively checked the #12479 ACs: root cause identified, already-read messages filtered from count/preview/priority, genuine unread/non-message wakes preserved, and a regression guard exists.
  • [IMPACT]: 75 - This removes high-noise wake floods that burn agent triage cycles and distort priority, but it is still localized to bridge-daemon digest construction.
  • [COMPLEXITY]: 45 - Moderate-low: one daemon helper plus one spawned-daemon integration test, with care needed around mailbox edge semantics and coalescing behavior.
  • [EFFORT_PROFILE]: Quick Win - Small code surface, direct runtime test, and high operational value for the wake substrate.

Approved. Per AGENTS.md §critical_gates, this means merge-eligible only; human operator still owns the merge.