Frontmatter
| title | >- |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jun 6, 2026, 2:18 AM |
| updatedAt | Jun 6, 2026, 3:09 PM |
| closedAt | Jun 6, 2026, 3:09 PM |
| mergedAt | Jun 6, 2026, 3:09 PM |
| branches | dev ← fix/12479-wake-read-filter |
| url | https://github.com/neomjs/neo/pull/12607 |
🚨 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.

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.readAtwhen a delivery edge exists, otherwiseMESSAGE.readAt),flushSubscription()recomputesNand priority after filtering, and the new test exercises a spawneddaemon.mjswith 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_TOedges for the same(messageId, recipient)ever surface,LIMIT 1makes 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 #12479in PR body). - #12479 labels checked via
gh issue view:bug,ai,model-experience; notepic. - 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.mjsplus real SQLite graph intest/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_requestat959972df8d9563032887172552e9502b8f799412. - 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 checksbefore 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.
Resolves #12479
The bridge daemon's wake digest now reconciles against actual mailbox read-status before counting. Previously
flushSubscriptioncounted 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 viaisMessageReadFor(db, messageId, recipient)(the per-recipientDELIVERED_TOedgereadAtif that edge exists, else theMESSAGEnodereadAt— mirroringMailboxService.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 influshSubscription'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-edgereadAt, else nodereadAt) is lifted directly fromMailboxService.getReadAtForMessagerather than re-derived, and thejson_extract(data, '$.properties.readAt')path matches what the graph RLS clause already uses.Test Evidence
DELIVERED_TOreadAtset) and one unread message into the same coalesce window; asserts the digest reports1 new messagespreviewing only the unread one, and never names the read one.Post-Merge Validation
Commits
959972df8— reconcile wake digest against read-status (helper + filter/suppress/recount influshSubscription+ integration test).Authored by Claude Opus 4.8 (Claude Code). Session c4f7dff5-d871-420e-ab99-7b736f0fe39d.