BE-4000 derive transplant onto merged develop (2026-07-29)

Companion to BE-3657 error_stack Adoption (the master error-stack decision trail). Records the 2026-07-29 events: MR !585 merged, and BE-4000 (the derive + DX v2 branch) transplanted onto the new develop, with the opl create_orders blocker and its resolution.

!585 merged, develop advanced

  • MR !585 (BE-3657 phase 3, adapters) MERGED via SQUASH at 07:15 UTC 2026-07-29 (squash c8bf3c84, merge commit f84db378). develop advanced 29ed8e34 f84db378; it now contains phase 3.
  • The opl create_orders review thread resolved by fait accompli: it merged WITH the restore in place (phase-3 tip c3519e73 “restore opl order creation” was the merged head), so create_orders/OplOrder are now ON develop. krisztian’s “not related to the ticket” / Andras’s “should we keep it then?” never formally resolved; kept by default. Any later removal is a separate cleanup PR.

Transplant boundary landmine (the handover command was stale)

Stale transplant one-liner

The handover said git rebase --onto origin/develop feature/BE-3657-phase3 feature/BE-4000. WRONG after the 2026-07-28 phase3 rebase: that rebase re-wrote phase3’s commit SHAs, so feature/BE-3657-phase3 (now c3519e73) no longer excludes BE-4000’s orphaned old-phase3 base. The range feature/BE-3657-phase3..feature/BE-4000 = 54 commits (29 already-merged old-phase3 + 25 derive). Running it would replay the 29 phase3 commits onto a develop that already has them, i.e. conflict hell.

  • CORRECT boundary = BE-4000’s true base ee84ea50 (“restore ebs upload block layout”). ee84ea50..feature/BE-4000 = exactly the 25 derive commits. Verified: merge-base(BE-4000, develop) = 4c3442df; 4c3442df..ee84ea50 = 29 old-phase3 commits (content now in develop via the squash); 29 + 25 = 54.
  • Transplant command: git rebase --onto origin/develop ee84ea50 feature/BE-4000.
  • General lesson: when a base branch is rebased AFTER you stack on it, base..yours stops excluding what you assume. Check commit counts + merge-base before any transplant, never trust a handover’s rebase one-liner blindly.

opl create_orders: dead code AND the sole transplant blocker

  • develop’s create_orders + OplOrder (mando-lib/src/adapter/alpiq/opl.rs) were restored in the OLD raw-Report<OplError> idiom (+94 lines). grep on develop: ZERO callers of either (only their own definitions), i.e. DEAD code.
  • It never existed on BE-4000’s base, so the derive sweep never transformed it. The derive design deliberately DELETED the lifting From<Report<C>> for MandoReport<C> in favor of an explicit .lift(). Transplanting the derive branch on top left create_orders untransformed, causing 20 compile errors, all in opl.rs (Report<OplError>: Error not satisfied / no report route).
  • The transplant auto-merged create_orders SILENTLY (no conflict markers) because it comes from the rebase target (develop), not from a replayed commit. First attempt (subagent) correctly ABORTED and restored the branch rather than guess on financial code.
  • Decision (Andras): SWEEP it into BE-4000 (not remove the dead code). Fix = 7 .lift() edits mirroring the live sibling create_strategies in the same file: get_algorithm().await.lift()?, .map_err(report_polars).lift()? x6, get_token().await.lift()?, handle_non_status_response(...).lift(). .lift() is type-only Report<C> -> MandoReport<C>, emission-preserving. The InvalidUrl {..}.into_report()? born-lifted site was left untouched. Landed as a trailing fix: adopt report derive for opl create orders commit (git rebase -i unsupported in harness, so no mid-history fold; BE-4000 squash-merges anyway).
  • Lesson: a code restore added mid-review to placate a “why was this deleted?” question can silently block a stacked branch authored before the restore existed.

RTK false-greens clippy output (new; companion to the test-gate trap)

  • A cargo clippy --release --all-features through RTK showed a 66-line filtered summary “0 warnings, 0 errors” exit 0, while RTK’s full 1692-line log had 20 error[...] + “could not compile mando_lib”. Exit code proved nothing.
  • Mitigation: run gates via rtk proxy cargo ... and check the FULL log (grep error[ / could not compile / a genuine Finished), never the filtered tail. Extends the known RTK test-gate false-green (bare cargo test ... -- --test-threads=1 gets rewritten into a filter that runs nothing).

Result + current state (updated 2026-07-30)

  • MR !592 OPEN (feat: add mando error attribute macro and adopt report derive, Closes BE-4000, reviewers krisztian.fekete1/gabor.nagy6/balint.budavoelgyi/jozsef.nagy1). feature/BE-4000 @ 0bc5fef9, 28 commits on develop tip cc3801de.
  • After the initial push (930c2413), two renames + a rebase landed (see Renames + rebase onto the mando-repository refactor 2026-07-30): .lift() .into_mando_result() (671c1eb2), crate mando-flow-step-derive mando-macros (0e359086), then rebase onto develop cc3801de (0bc5fef9) resolving 3 import conflicts.
  • Gates (real, RTK-proxied): clippy clean; test —lib = 649 passed / 0 failed / 32 ignored; mando_macros trybuild green.
  • Conflict RESOLVED: !592 has_conflicts=false, merge_status=can_be_merged. Standing blocker: detailed_merge_status=requested_changes (krisztian’s formal review, from his “conflict” comment; a push does not auto-clear it, needs his re-review now the conflict is fixed).
  • BE-4014 Datadog Error-Rendering Test Harness still ready to MR anytime on Andras’s yes; independent.

Renames + rebase onto the mando-repository refactor (2026-07-30)

  • .lift() .into_mando_result() (Andras chose the type-accurate name over into_mando_report/lift_report). The LiftExt trait IntoMandoResultExt; the MandoReport::lift(report) constructor MandoReport::from_report (incl. the proc-macro codegen). 50 call sites, exhaustive; commit 671c1eb2.
  • Crate mando-flow-step-derive mando-macros (the “flow” verb was wrong once BE-4000 put the generic #[mando_error] attribute macro in it, alongside the flow-step derives StepConfig/FlowStepService/ParamEnum/ParamMeta). Folded into BE-4000 since BE-4000 caused the misnomer. 59 files: dir git-mv + package name = "mando_macros" + workspace member/dep + ~38 imports. Naming split preserved: hyphens for dir/paths/members, underscores for package name + dep keys. Commit 0e359086.
  • Rebase onto develop cc3801de: develop had advanced (a day) with a big repository-layer refactor (new mando-repository crate, ea3311a0/d72091a3) + tensor type + direct-postgres mode. Only 3 of 9 predicted files actually conflicted (adapter/volue/ems.rs, service/volue/ems.rs, workflow/mod.rs) - all import-block collisions where develop’s RepoError -> MandoServiceError refactor landed next to BE-4000’s derive imports. Resolved by keeping develop’s MandoServiceError / flattened mando_repository::RepoError + BE-4000’s use mando_macros::mando_error; and MandoError marker imports; unused RepoError imports dropped. opl.rs replayed clean. Result 0bc5fef9.
  • LESSON: a develop-side crate-extraction refactor collides with a feature branch mostly in imports + Cargo.toml, not logic, IF the feature branch never touched the moved code. The comm -12 file-intersection gauge (9 candidates) over-predicted; 3 were real.

into_mando_result authoring cheatsheet: when does bare ? work

In a fn returning MandoResult<T, YourError>, bare ? on Result<_, S> works IFF: (1) S: std::error::Error + Send + Sync + 'static, AND (2) #[cause(S)] is declared on a variant of YourError (generates MandoFrom<S>; the blanket impl<S,E> From<S> for MandoReport<E> where E: MandoFrom<S> does the rest). Otherwise, one verb:

  • raw error_stack::Report<C> (a tower fn, or .map_err(report_polars)) .into_mando_result()? (Report isn’t a std Error, the blanket can’t see it).
  • an already-born MandoReport<Inner> you fold into your enum with a static message .wrapped()? (needs #[cause(wraps(Inner, message="..."))]).
  • constructing a NEW error .into_report() (value) / .reported()? (on Result<_, YourError>). Keystone: MandoReport deliberately does NOT impl std::error::Error (the anyhow trick) - that is what makes the blanket From coherent, and why the two transitions above need explicit verbs instead of ?. The #[diagnostic::on_unimplemented] notes on MandoFrom/MandoWraps name the missing verb/declaration for you.

Roadmap (unchanged, nothing half-done blocking)

  • After BE-4000 MRs + merges: UNWALLING phase (convert the 5 frozen auth towers ems/atp/mdr/opl/position_manager so reports flow to the step boundary); the into_bare_report() helper decision lives there. Then phase 4 #[error_meta(...)]. Archiver adapter deferred.
  • UPDATE 2026-08-04: unwalling DONE. BE-4047 began it, BE-4067 whole-mando error unwalling FINISHED it - zero production raw-Report left in mando-lib (git grep into_mando_result -- mando-lib/src = 0). The into_bare_report() question resolved into the shared wrap_http_call helper (multi-field + HttpContext reattach, so not a #[cause] route). Phase 4 #[error_meta] + downstream-service conformance remain open.