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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! Lifecycle hooks for `Model` CRUD operations.
//!
//! Six methods, each defaulted to a no-op that returns `Ok(())`. Adopters
//! `impl ModelHooks for MyModel` selectively — methods they don't override
//! stay no-op.
//!
//! T1.2 adds the sealed [`HasHooks`] marker trait that the macro layer
//! emits in T1.3 (`#[model(hooks)]`) to gate monomorphic dispatch in
//! T1.4–T1.6. Until the macro layer lands, `HasHooks` has zero impls and
//! [`ModelHooks`] remains the public adopter trait — nothing dispatches
//! against either yet.
//!
//! # Async-fn-in-trait via `impl Future + Send`
//!
//! Each method returns `impl Future<Output = Result<(), DjogiError>> + Send`
//! rather than going through `BoxFuture` / `Pin<Box<...>>` / the
//! `async-trait` macro. The default body desugars to a state machine the
//! compiler elides at call sites that keep the no-op default — there is no
//! heap allocation, no virtual dispatch, and no `'static` escape on `Self`.
//! T1.8 verifies the zero-overhead claim with `cargo asm`.
//!
//! # Receiver shape
//!
//! `before_*` methods take `&mut self` so the hook body can mutate the
//! model before it is written to the database (e.g. setting `created_by`,
//! normalising a slug). `after_*` methods take `&self` because the row is
//! already persisted — there is nothing to mutate that the database would
//! pick up. Every method takes `&mut DjogiContext` so the hook body
//! inherits the surrounding tenant scope, [`AuthContext`], and the
//! `on_commit` queue (Phase 8 §D1).
//!
//! # Sequencing and error semantics
//!
//! The CRUD callers wire hooks into the canonical
//! `before → DB → outbox → after → on_commit drain` sequence (Phase 8 §D3).
//! Returning `Err` from any hook aborts the operation: the surrounding
//! transaction (if one is open) rolls back via the standard `?` propagation
//! path, and no `after_*` hook fires for an aborted operation (Phase 8 §D4).
//!
//! [`AuthContext`]: crate::auth::AuthContext
use crate::;
use Future;
/// Lifecycle hooks an adopter implements for a `Model` to participate in
/// CRUD-time side effects.
///
/// All six methods default to a no-op that returns `Ok(())`. Adopters
/// override only the methods they care about — the rest stay no-op and
/// remain zero-cost at the call site. See the module-level docs for the
/// `before → DB → outbox → after → on_commit drain` sequence and the
/// `Err`-aborts-operation contract.
///
/// `Send` is required on every returned future because Djogi's CRUD
/// terminals are themselves `Send` futures driven by the multi-threaded
/// Tokio runtime — a hook future that is not `Send` would refuse to compile
/// at the call site rather than at the trait definition.
/// Sealed marker trait — the type-level gate the `#[model(hooks)]` macro
/// (T1.3) emits to opt a model into hook dispatch.
///
/// `HasHooks` carries no methods, no associated types, and no lifetime
/// parameters: it is purely a witness that lets the CRUD terminals
/// (T1.4–T1.6) branch monomorphically between the no-op fast path and the
/// hook-dispatch path. Without an `impl HasHooks for M`, the generic
/// `<M as HasHooks>::…` call sites collapse to dead code that LLVM
/// removes regardless of LTO settings (T1.8 verifies this with
/// `cargo asm`).
///
/// # Sealed via `private::Sealed`
///
/// The supertrait `private::Sealed` lives in a module-private inner
/// module so adopter code working only against the public surface
/// (`djogi::hooks::HasHooks`, `djogi::ModelHooks`) cannot name the seal
/// and therefore cannot write `impl HasHooks for MyType` from the public
/// path. The single emitter is the `#[model(hooks)]` proc macro, which
/// routes through `::djogi::__private::hooks::Sealed` (re-exported below)
/// per the macro-path-routing convention.
///
/// **Seal-by-convention caveat.** Both `::djogi::__private::hooks::Sealed`
/// and `::djogi::hooks::__seal::Sealed` re-export the same supertrait, so
/// the `Sealed` trait is reachable cross-crate by name through either
/// path — downstream code that deliberately reaches into `__private` or
/// the `#[doc(hidden)] __seal` module (in violation of the "we reserve
/// the right to break that code in any future release without notice"
/// contract documented at the crate root) could still hand-roll an
/// `impl HasHooks` chain. This matches the convention used by
/// [`crate::visage_boundary`] for `DjogiVisageOf` / `VisageSealed` and
/// by [`crate::primary_key`] for the `PkSealToken` witness — Rust has no
/// way to mark a trait "implementable only inside this crate" when its
/// supertrait must be reachable from a separate proc-macro-emitting
/// crate, and we accept the convention-level seal as the trade-off.
/// Macro-callable re-exports.
///
/// The `#[model(hooks)]` proc macro (T1.3) emits paths through
/// `::djogi::__private::hooks::*` — never `::djogi::hooks::__seal::*`
/// directly — so that this module is the single coupling point between
/// the emitted code and the framework's seal machinery. Adopter code that
/// only writes `impl ModelHooks for M` and `#[model(hooks)]` never names
/// any symbol in this module.