Skip to content

feat(notifications): ADR-0030 P0 — single ingress + layered model (framework)#1434

Merged
os-zhuang merged 4 commits into
mainfrom
claude/admiring-gauss-xRNfD
Jun 1, 2026
Merged

feat(notifications): ADR-0030 P0 — single ingress + layered model (framework)#1434
os-zhuang merged 4 commits into
mainfrom
claude/admiring-gauss-xRNfD

Conversation

@os-zhuang
Copy link
Copy Markdown
Contributor

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 ingressMessagingService.emit(EmitInput) (topic/audience/payload/severity/dedupKey/source/actorId/organizationId):

  1. writes the L2 sys_notification event (idempotent on dedupKey),
  2. resolves the audience to recipients (explicit ids/emails inline; role:/team:/owner_of: forwarded but deferred to P1),
  3. fans (channel × recipient) deliveries out to channels.

Object model

  • sys_notification re-modeled → event (topic, payload json, severity, dedup_key, source_*, actor_id). Removed recipient_id/is_read/read_at/type/title/body/url/actor_name and the inbox actions/views.
  • sys_inbox_message: + notification_id/delivery_id, dropped read, added the user mine view.
  • New sys_notification_receipt — the read-state spine (delivered|read|clicked|dismissed), keyed (notification_id, user_id, channel). The inbox channel writes a delivered receipt on materialization (best-effort).

Producers re-routed through emit() (no more direct sys_notification writes)

  • Flow notify node → EmitInput (title/body/url in payload).
  • Collaboration @mentioncollab.mention, assignment → collab.assignment (plugin-audit), both with a dedupKey. Messaging is resolved lazily at hook time.

Data migration (idempotent, not auto-run) — migrate-sys-notification-to-event splits legacy inbox rows into sys_inbox_message + receipts and rewrites the event row.

App-nav — account inbox → sys_inbox_message (mine); setup → notification event log.

⚠️ Breaking (cross-repo)

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 MessagingServicePlugin to be installed (they degrade to a warn otherwise, like the notify node).

Testing

All affected suites pass locally:

  • service-messaging: 31 ✓
  • service-automation: 105 ✓
  • metadata (incl. migration): 245 ✓
  • platform-objects: 78 ✓

Follow-ups

  • Mark-read write path (receipt upsert action/endpoint) — tail of P0 with objectui, or P1.
  • Regenerate platform-objects/.../translations/*.generated.ts (stale sys_notification field labels; harmless).
  • P1 outbox + RecipientResolver; P2 subscription/preference; P3 channels/templates/digest.

🤖 Generated with Claude Code


Generated by Claude Code

…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
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 1, 2026 4:17am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests size/xl labels Jun 1, 2026
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 os-zhuang marked this pull request as ready for review June 1, 2026 04:12
@os-zhuang os-zhuang merged commit 13632b1 into main Jun 1, 2026
12 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants