Apply `op` to `engine`, returning an outcome without panicking. Steps: 1. Shape validation — on error: `Rejected(ShapeInvalid)`. 2. Schema HLC check — `None` → `Rejected(ArrayUnknown)`; `op.header.schema_hlc > local` → `Rejected(SchemaTooNew)`. 3. Idempotency — already seen → `Idempotent`. 4. Dispatch to `apply_put`/`apply_delete`/`apply_erase`. Engine errors that indicate corruption (`SegmentCor
(engine: &mut E, op: &ArrayOp)
| 117 | /// 5. Tile-cache invalidation. |
| 118 | /// 6. Return `Applied`. |
| 119 | pub fn apply_op<E: ApplyEngine>(engine: &mut E, op: &ArrayOp) -> ArrayResult<ApplyOutcome> { |
| 120 | // 1. Shape validation. |
| 121 | if let Err(e) = op.validate_shape() { |
| 122 | return Ok(ApplyOutcome::Rejected(ApplyRejection::ShapeInvalid { |
| 123 | detail: e.to_string(), |
| 124 | })); |
| 125 | } |
| 126 | |
| 127 | // 2. Schema HLC gating. |
| 128 | match engine.schema_hlc(&op.header.array)? { |
| 129 | None => { |
| 130 | return Ok(ApplyOutcome::Rejected(ApplyRejection::ArrayUnknown { |
| 131 | name: op.header.array.clone(), |
| 132 | })); |
| 133 | } |
| 134 | Some(local_schema) if op.header.schema_hlc > local_schema => { |
| 135 | return Ok(ApplyOutcome::Rejected(ApplyRejection::SchemaTooNew { |
| 136 | local: local_schema, |
| 137 | op: op.header.schema_hlc, |
| 138 | })); |
| 139 | } |
| 140 | Some(_) => {} |
| 141 | } |
| 142 | |
| 143 | // 3. Idempotency. |
| 144 | if engine.already_seen(&op.header.array, op.header.hlc)? { |
| 145 | return Ok(ApplyOutcome::Idempotent); |
| 146 | } |
| 147 | |
| 148 | // 4. Dispatch. |
| 149 | let dispatch_result = match op.kind { |
| 150 | ArrayOpKind::Put => engine.apply_put(op), |
| 151 | ArrayOpKind::Delete => engine.apply_delete(op), |
| 152 | ArrayOpKind::Erase => engine.apply_erase(op), |
| 153 | }; |
| 154 | |
| 155 | if let Err(e) = dispatch_result { |
| 156 | // Corruption-grade errors propagate; everything else becomes a rejection. |
| 157 | match &e { |
| 158 | ArrayError::SegmentCorruption { .. } | ArrayError::HlcLockPoisoned => return Err(e), |
| 159 | _ => { |
| 160 | return Ok(ApplyOutcome::Rejected(ApplyRejection::EngineRejected { |
| 161 | detail: e.to_string(), |
| 162 | })); |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | // 5. Tile invalidation. |
| 168 | engine.invalidate_tile(&op.header.array, &op.coord)?; |
| 169 | |
| 170 | // 6. Success. |
| 171 | Ok(ApplyOutcome::Applied) |
| 172 | } |
| 173 | |
| 174 | // ─── MockEngine (test / test-utils only) ──────────────────────────────────── |
| 175 |