feat(notifications): ADR-0030 P0 — single ingress + layered model (framework)#1434
Merged
Conversation
…amework)
Realize the framework side of ADR-0030: every producer now publishes through
one ingress and the in-app inbox is a materialization of delivery, not a thing
producers write.
- MessagingService.emit(EmitInput): writes the L2 sys_notification event
(idempotent on dedupKey), resolves audience, fans out to channels; returns
{ notificationId, deduped, deliveries, ... }.
- Re-model sys_notification → event (topic/payload/severity/dedup_key/source/
actor); drop recipient/read/title/body and the inbox actions+views.
- sys_inbox_message: add notification_id/delivery_id, drop read flag, add the
user `mine` view. New sys_notification_receipt holds read-state; the inbox
channel writes a `delivered` receipt on materialization.
- Route producers through emit(): the notify flow node, and collaboration
@mention/assignment (plugin-audit) — no more direct sys_notification writes.
- Idempotent data migration migrate-sys-notification-to-event splits legacy
inbox rows into sys_inbox_message + receipts and rewrites the event.
- App-nav: account inbox → sys_inbox_message (mine); setup → event log.
- Tests updated/added across messaging, notify-node, and the migration.
objectui (Console bell) repoint and phases P1–P3 remain — see
docs/handoff/adr-0030-notification-convergence.md.
https://claude.ai/code/session_015pRGvrm3zrk5m8YvZhkAmF
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Post-ADR-0030 collaboration @mention/assignment deliver through the messaging pipeline, so add `messaging` to ALWAYS_ON_CAPABILITIES — every non-minimal preset now starts MessagingServicePlugin and the bell lights up out of the box. `--preset minimal` still opts out. https://claude.ai/code/session_015pRGvrm3zrk5m8YvZhkAmF
- migration: columnExists now falls through to information_schema on Postgres (PRAGMA threw inside the shared try/catch, making the migration silently no-op on every non-SQLite DB); add null guards on the result unwrap. - migration: use one topic for both the inbox row and the rewritten event so a legacy row with empty `type` can't leave them disagreeing. - emit: normalize empty-string source_object/source_id to null so the (source_object, source_id) index never keys on ''. - audit assignment: scope dedupKey by the record write-version (updated_at) so a legitimate re-assignment back to a prior owner isn't permanently suppressed. - cloud capability-loader: load `messaging` whenever `audit` is required, so collaboration notifications don't no-op on per-project kernels (which have no always-on slate, unlike `objectstack serve`). - migration test: add a Postgres-style driver case (PRAGMA throws → fallback). - handoff: document best-effort P0 dedup, event-log growth, and the mark-read write path required for the objectui cut-over. https://claude.ai/code/session_015pRGvrm3zrk5m8YvZhkAmF
os-zhuang
added a commit
that referenced
this pull request
Jun 1, 2026
…n contributions (#1436) ADR-0029 D7 — navigation-contribution mechanism (UI-layer analog of object own/extend): NavigationContributionSchema + manifest.navigationContributions; SchemaRegistry.registerAppNavContribution() with lazy merge in getApp/getAllApps by group id + priority; engine wiring. Setup app becomes a shell of group anchors with platform-objects entries in SETUP_NAV_CONTRIBUTIONS; plugin-webhooks contributes its Webhooks entries into group_integrations. Merged main (#1434 notification convergence); rendered Setup nav unchanged.
This was referenced Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Realizes the framework side of ADR-0030 P0 — the critical "seams" phase. Establishes the single-ingress rule: every notification producer publishes through one API, and the in-app inbox becomes a materialization of delivery rather than something producers write directly.
Per the build spec, P0 spans framework + objectui. objectui (the Console bell) is a separate repo, so this PR is the framework half; the bell repoint and the destructive data migration are documented for a coordinated cut-over in
docs/handoff/adr-0030-notification-convergence.md.Scope was confirmed with the requester: destructive re-model + migration now, P0 framework-side only, + handoff doc.
What changed
Single ingress —
MessagingService.emit(EmitInput)(topic/audience/payload/severity/dedupKey/source/actorId/organizationId):sys_notificationevent (idempotent ondedupKey),role:/team:/owner_of:forwarded but deferred to P1),(channel × recipient)deliveries out to channels.Object model
sys_notificationre-modeled → event (topic,payloadjson,severity,dedup_key,source_*,actor_id). Removedrecipient_id/is_read/read_at/type/title/body/url/actor_nameand the inbox actions/views.sys_inbox_message: +notification_id/delivery_id, droppedread, added the usermineview.sys_notification_receipt— the read-state spine (delivered|read|clicked|dismissed), keyed(notification_id, user_id, channel). The inbox channel writes adeliveredreceipt on materialization (best-effort).Producers re-routed through
emit()(no more directsys_notificationwrites)notifynode →EmitInput(title/body/url inpayload).@mention→collab.mention, assignment →collab.assignment(plugin-audit), both with adedupKey. Messaging is resolved lazily at hook time.Data migration (idempotent, not auto-run) —
migrate-sys-notification-to-eventsplits legacy inbox rows intosys_inbox_message+ receipts and rewrites the event row.App-nav — account inbox →
sys_inbox_message(mine); setup → notification event log.The Console bell reads
sys_notification.{recipient_id,is_read,title,body}, which no longer exist. Run the objectui repoint + the data migration together — see the handoff doc for the exact cut-over sequence and objectui changes.Also note: collaboration notifications now require
MessagingServicePluginto be installed (they degrade to a warn otherwise, like thenotifynode).Testing
All affected suites pass locally:
Follow-ups
platform-objects/.../translations/*.generated.ts(stalesys_notificationfield labels; harmless).RecipientResolver; P2 subscription/preference; P3 channels/templates/digest.🤖 Generated with Claude Code
Generated by Claude Code