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
//! Composite and delegating reader/processor/writer components (#146).
//!
//! Every type here is a monomorphized decorator: no `Boxed*` erasure is
//! introduced by this module, so a pipeline built entirely from these types
//! keeps the ADR-0008 zero-per-item-allocation property. Heterogeneous
//! delegates remain possible by naming `BoxedReader`/`BoxedProcessor`/
//! `BoxedWriter` as the delegate type parameter, at the same erasure boundary
//! ADR-0008 already accepts.
//!
//! # Composition semantics (Gate E)
//!
//! A wrapper's advertised capability is the meet (intersection) of its
//! delegates', never their union:
//!
//! - **ordering** — [`CompositeReader`] and [`FanOutWriter`] preserve each
//! delegate's relative item order; if any delegate is order-sensitive, so
//! is the composite.
//! - **restartability** — none of these types add durable state; the
//! composite is restartable only if every delegate it holds is (typically
//! via a separately registered [`crate::ItemStream`], never hidden by this
//! wrapper — see the module-level docs on state below).
//! - **checkpoint/state** — a delegate that implements [`crate::ItemStream`]
//! is registered with the owning [`crate::ChunkStep`] independently
//! (`with_item_stream`), under its own namespace; these composite types
//! never swallow or proxy that registration, so no delegate's durable state
//! is hidden and no two delegates' namespaces can collide through this
//! wrapper.
//! - **thread safety** — [`ChainProcessor`] and [`FanOutWriter`] are
//! `Send + Sync` exactly when every delegate is; no synchronization is
//! added here (contrast [`crate::item_components::sync`]).
//! - **error classification** — a delegate's [`ReaderError`]/
//! [`ProcessorError`]/[`WriterError`] is returned unchanged; these wrappers
//! never reclassify it.
//! - **close ordering** — not applicable to these types directly (they own no
//! `ItemStream` themselves); see the module docs above.
//! - **stop propagation** — a [`ReadOutcome::Stopped`], [`ProcessOutcome::Stopped`],
//! or [`WriteOutcome::Stopped`] observed from any delegate stops the
//! composite immediately and is returned as-is; later delegates in the same
//! call are never invoked.
use crate::;
/// Reads from an ordered sequence of homogeneous delegate readers, advancing
/// to the next delegate only once the current one reports
/// [`ReadOutcome::EndOfInput`].
///
/// A [`ReadOutcome::Stopped`] or [`ReaderError`] from any delegate is
/// returned immediately; later delegates are not consulted for that call.
/// The composite's position (which delegate is "current") is never advanced
/// on a failing call, so a retry -- the framework's fault-retry contract
/// re-invokes the same reader instance without rewinding it, exactly as
/// [`crate::item_components::aggregate`]'s module docs describe -- resumes
/// at the same delegate rather than skipping to the next one; see
/// `composite_reader_retry_after_failure_resumes_the_same_delegate` in the
/// evidence file below.
///
/// # Contract
///
/// - **Input/output**: `I`, same as every delegate `R`.
/// - **State/checkpoint**: the current-delegate index is in-memory only, not
/// restartable by itself; a restartable composite pairs each delegate with
/// its own [`crate::ItemStream`] and registers them independently (see the
/// module docs).
/// - **Ordering**: concatenates delegates in declared order; order-sensitive
/// if any delegate is.
/// - **Thread safety**: used exclusively (`&mut self`) like every reader.
/// - **Bounded resource**: none beyond the delegates' own.
/// - **Cancellation**: propagated from whichever delegate observes it.
/// - **Close**: nothing to close.
/// - **Malformed input**: propagates the failing delegate's [`ReaderError`]
/// unchanged.
/// - **Support tier**: first-party.
/// - **Evidence**: `crates/oxide-batch-test/tests/item_components_composite.rs`.
/// Chains two [`crate::ItemProcessor`]s: `P1`'s output feeds `P2`'s input.
///
/// A [`ProcessOutcome::Filtered`] or [`ProcessOutcome::Stopped`] from `P1`
/// short-circuits without invoking `P2`. Nest another `ChainProcessor` as
/// `P1` or `P2` to compose more than two stages.
///
/// # Contract
///
/// - **Input/output**: `I -> O` via the intermediate type `M`.
/// - **State/checkpoint**: stateless (assuming both delegates are).
/// - **Ordering**: preserves order; filters/stops exactly when either stage
/// does.
/// - **Thread safety**: `Send + Sync` whenever `P1` and `P2` are.
/// - **Reentrancy**: fully reentrant when both delegates are.
/// - **Transaction/delivery**: not applicable.
/// - **Bounded resource**: none beyond the delegates' own.
/// - **Cancellation**: the call-scoped stop token is shared by both stages;
/// `P1`'s own `Stopped` outcome takes priority over invoking `P2`.
/// - **Close**: nothing to close.
/// - **Error classification**: whichever delegate fails, its
/// [`ProcessorError`] is returned unchanged.
/// - **Support tier**: first-party.
/// - **Evidence**: `crates/oxide-batch-test/tests/item_components_composite.rs`.
/// Fans a chunk out to an ordered sequence of homogeneous delegate writers.
///
/// When the call is enlisted in a business transaction, the single
/// `&mut dyn BusinessTransaction` is reborrowed sequentially, once per
/// delegate: no two delegates ever hold it simultaneously, no delegate's
/// reference escapes its own call, and this wrapper never opens a second
/// transaction or connection (the enlisted [`WriteContext`] transaction
/// model, unchanged).
///
/// A [`WriteOutcome::Stopped`] or [`WriterError`] from any delegate stops the
/// fan-out immediately; later delegates in that call are not invoked.
///
/// # Contract
///
/// - **Input/output**: `[O]`, same as every delegate `W`.
/// - **State/checkpoint**: stateless.
/// - **Ordering**: each delegate receives the full batch in its original
/// order; order-sensitive if any delegate is.
/// - **Thread safety**: `Send + Sync` whenever `W` is; requires `O: Sync`
/// because the borrowed batch is held across each delegate's `await`
/// (ADR-0008's generic-composite bound).
/// - **Reentrancy**: fully reentrant when every delegate is.
/// - **Transaction/delivery**: never claims a stronger mode than every
/// delegate supports; see the reborrow guarantee above.
/// - **Bounded resource**: none beyond the delegates' own.
/// - **Cancellation**: checked once before the first delegate, and again via
/// each delegate's own outcome.
/// - **Close**: nothing to close.
/// - **Error classification**: whichever delegate fails, its [`WriterError`]
/// is returned unchanged.
/// - **Support tier**: first-party.
/// - **Evidence**: `crates/oxide-batch-test/tests/item_components_composite.rs`.