1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! The EventRegistrationTargetResolver — the mass_mailing_event bridge
//! target's typed resolver port (hand-written; user-owned; see
//! `metaphor.codegen.yaml`).
//!
//! The `event_registration` target model mails an event's registrants: the
//! audience lives in the events module's registration read model (attendee
//! rows carrying an email column), and this module takes NO code
//! dependency on the events module — the read crosses as a DECLARED seam,
//! exactly like the crm/sale bridge targets:
//!
//! - this module owns the trait and its typed error;
//! - the host service composes ONE implementation that resolves
//! registrations through the events module's read surface, applying the
//! events module's own declared mail-eligibility law
//! (`state IN ('open','done') AND active`, plus not soft-deleted) and
//! mapping the SAME whitelisted typed domain DSL every target sees onto
//! the registration columns;
//! - the seam stays one-way and read-only: the port answers recipients,
//! it never writes, never seats, and never widens into a generic events
//! query surface.
//!
//! Fail-closed posture (two layers, never a silent zero):
//!
//! 1. The REGISTRY layer is the operational default: a mailing targeting
//! `event_registration` that reaches the send walk with no composed
//! resolver PARKS loudly (state stays retryable,
//! `metadata.send_error` names the missing seam) — the same
//! never-silent contract the crm/sale targets run under. This is
//! deliberate: a permanently-refusing entry seeded into the resolver
//! registry would fail the whole sweep (`Conflict`) and starve
//! UNRELATED mailings of their send turn, while the park isolates the
//! misconfigured mailing and keeps the queue draining.
//! 2. [`RefusingEventRegistrationTarget`] is the deny-by-default
//! implementation for DIRECT port consumers (the
//! SaleInvoicedAmountPort shape): every call refuses with the typed
//! [`EventRegistrationTargetError::NotComposed`] naming the install
//! verb.
//!
//! The *_sms twin rides the SAME target on the existing sms channel (the
//! MVX-2 collapse: no twin enum, no twin module); the sms send walk itself
//! stays uncomposed at this seam, so nothing here promises delivery. No
//! track-shaped target exists — the track twins are deferred by owner
//! ruling until a tracks substrate is ratified in the events module.
use Arc;
use async_trait;
use crate;
/// Why an event-registration target resolution failed.
/// The events-registrations bridge target's resolver port: ONE method by
/// design — resolve the SAME whitelisted typed domain DSL every target
/// sees into email recipients. Nothing else about the events module
/// crosses.
///
/// The implementation decides which whitelisted fields carry an honest
/// column on the registration row (email, attendee name, company name do;
/// person-name and country fields do not) and must REFUSE — not silently
/// drop — a domain term it cannot bind.
/// The deny-by-default implementation for direct port consumers: every
/// call refuses with the typed
/// [`EventRegistrationTargetError::NotComposed`]. The registry layer's
/// park (see the module doc) remains the operational fail-closed default
/// for the send walk.
;
/// The typed port's adapter into the per-target resolver registry: wraps
/// an [`EventRegistrationTargetResolver`] as the generic
/// `TargetRecipientResolver` keyed `event_registration` (the
/// `PartyResolverAdapter` shape). The typed error's `Display` crosses as
/// the refusal string — the send walk parks on it loudly.