Skip to content

Commit db8d2ef

Browse files
committed
wip(service-messaging): delivery title names the recipient user
Claude-Session: https://claude.ai/code/session_01WNppskqdULWrMoXfU9sgo1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6bc8cc6 commit db8d2ef

1 file changed

Lines changed: 50 additions & 12 deletions

File tree

‎packages/services/service-messaging/src/objects/notification-delivery.object.ts‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ export const NotificationDelivery = ObjectSchema.create({
7171
},
7272
// ADR-0030 Layer 4 — the reference lives in the docblock above, not in the copy.
7373
description: 'Durable per-recipient × channel delivery outbox.',
74-
// [ADR-0079] The record title is `display_title`, a text formula over the
75-
// same two columns `titleFormat` names. With no pointer declared, the
76-
// registry's designate-only pass stamped `nameField: 'id'` (the first
77-
// title-eligible field), so a renderer honouring ADR-0079's order (an
78-
// explicit `nameField` wins over `titleFormat`) drew the raw id as the
79-
// record page's H1. `titleFormat` stays for renderers that still read it
80-
// first; `notification-display-title.test.ts` holds the two to the same text.
74+
// [ADR-0079] The record title is `display_title`, a text formula. With no
75+
// pointer declared, the registry's designate-only pass stamped
76+
// `nameField: 'id'` (the first title-eligible field), so a renderer
77+
// honouring ADR-0079's order (an explicit `nameField` wins over
78+
// `titleFormat`) drew the raw id as the record page's H1. `titleFormat`
79+
// stays for renderers that still read it first; the formula names the
80+
// recipient as a PERSON where `titleFormat` names the raw recipient, so the
81+
// two no longer read the same text
82+
// (`../delivery-recipient-user.integration.test.ts`).
8183
displayNameField: 'display_title',
8284
nameField: 'display_title', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
8385
titleFormat: '{channel} → {recipient_id}',
@@ -87,13 +89,24 @@ export const NotificationDelivery = ObjectSchema.create({
8789
id: Field.text({ label: 'Delivery ID', required: true, readonly: true }),
8890

8991
// [ADR-0079] The record title (`nameField` above). A formula is computed
90-
// on read and has no stored column. `channel` and `recipient_id` are
91-
// both required, so the expression needs no null guard.
92+
// on read and has no stored column. It names the recipient as a PERSON
93+
// where the recipient is a user, one hop through `recipient_user_id`:
94+
// the user's `name`, or their `email` where the name is blank. An
95+
// address recipient (an empty `recipient_user_id`) reads as the address
96+
// itself, `recipient_id`. `channel` and `recipient_id` are both
97+
// required, so the address leg needs no null guard.
98+
//
99+
// `recipient_user_id` is read only through the hop and `recipient_id`
100+
// only bare: one formula may not read a field both ways
101+
// (`traversal-bare-and-traversed`). A reference the read cannot resolve
102+
// (a user this reader may not read) leaves the whole title null, as on
103+
// `sys_notification_preference`; it never falls back to the raw id,
104+
// which would read as an address.
92105
display_title: Field.formula({
93106
label: 'Title',
94107
returnType: 'text',
95-
expression: F`record.channel + ' → ' + record.recipient_id`,
96-
description: 'Record title: the delivery channel and its recipient (computed on read)',
108+
expression: F`record.channel + ' → ' + (isBlank(trim(record.recipient_user_id.?name.orValue(''))) ? record.recipient_user_id.?email.orValue(record.recipient_id) : record.recipient_user_id.?name.orValue(''))`,
109+
description: 'Record title: the delivery channel and the person it is for, or the address when the recipient is not a user (computed on read)',
97110
}),
98111

99112
notification_id: Field.text({
@@ -109,10 +122,19 @@ export const NotificationDelivery = ObjectSchema.create({
109122
maxLength: 255,
110123
description: 'FK → sys_notification (L2 event)',
111124
}),
125+
// The address the delivery is sent to — a `sys_user` id, or an email
126+
// address or phone number kept verbatim — and part of the dedup key.
127+
// NOT a `sys_user` reference: `RecipientResolver` keeps an email no user
128+
// holds verbatim, and the email and SMS channels send to an email-shaped
129+
// or phone-shaped recipient directly. The outbox writes under the system
130+
// context, which skips the engine's reference check, so a lookup here
131+
// would store every such address as a dangling reference. The person,
132+
// where there is one, is `recipient_user_id` below.
112133
recipient_id: Field.text({
113-
label: 'Recipient User',
134+
label: 'Recipient',
114135
required: true,
115136
searchable: true,
137+
description: 'The address the delivery is sent to: a user id, or an email address or phone number kept as given.',
116138
// [commit e4902d2b9] Referenced-column bound (route A, ruling 2026-08-24): a resolved
117139
// recipient is a `sys_user.id` (physical varchar(255), as above)
118140
// or an email-shaped value `RecipientResolver.resolveOne()` keeps
@@ -121,6 +143,22 @@ export const NotificationDelivery = ObjectSchema.create({
121143
// column. 255 admits both producers.
122144
maxLength: 255,
123145
}),
146+
// [#25194] The person `recipient_id` addresses, when it is a user's
147+
// id: stamped at enqueue by `MessagingService.enqueueDeliveries` from
148+
// one `sys_user` read per emit, and left empty for an address no user
149+
// holds. It exists for the record title (`display_title` above) and
150+
// is never read to route a delivery — the channels resolve
151+
// `recipient_id` themselves.
152+
//
153+
// `set_null` (the lookup default) on purpose: the delivery is history
154+
// and outlives the user, and an emptied reference titles the row by
155+
// `recipient_id` again. No `maxLength`: a reference holds the
156+
// referenced row's id, whose width is the parent's id column.
157+
recipient_user_id: Field.lookup('sys_user', {
158+
label: 'Recipient User',
159+
required: false,
160+
description: 'The user the delivery is for. Empty when the recipient is an email address or phone number no user holds.',
161+
}),
124162
channel: Field.text({
125163
label: 'Channel',
126164
required: true,

0 commit comments

Comments
 (0)