BE-3657 error_stack Adoption
Locked design decision to adopt the error_stack crate (=0.8.0) wholesale as mando’s error-handling substrate, replacing the ErrorCode-derive direction. Follow-on to BE-3656 APM Span Enrichment. Spec’d 2026-07-15/16; phases 1+2 MERGED 2026-07-23 via MR !580 (squash de7cd4c6, merge 4c3442df, zero review comments); phase 3 (plumbing + adapters) IN REVIEW since 2026-07-23 evening as MR !585 (feature/BE-3657-phase3 → develop, now @ f771805e, ~27 commits - see MR !585 opened (2026-07-23 evening)); review scope includes the 2026-07-23 short error.code decision (see Short error.code decision (2026-07-23)), the 2026-07-24 Caused-by error.stack renderer that REVISES locked decision 3 (see error.stack Caused-by renderer decision (2026-07-24)), and the 2026-07-24 error-authoring DX overhaul (report vocabulary, short error.details codes, native report signatures via the MandoReport newtype; the next MR, a derive(MandoReport) proc macro, is already spec’d - see Error-authoring DX overhaul (2026-07-24)). The dev environment runs a throwaway build carrying the phase 1+2 stack, field shapes verified in Datadog (see the dev prototype section).
For Agents
Status 2026-07-24: MR !580 (phases 1+2) MERGED (squash
de7cd4c6, merge4c3442df, zero review comments); phase 3 IN REVIEW as MR !585 “feat: adopt error stack across all adapters” (feature/BE-3657-phase3→develop, headf771805e, ~27 commits, pushed; reviewers krisztian.fekete1 / gabor.nagy6 / balint.budavoelgyi / jozsef.nagy1; branch pipeline was green ata58df341, local gates atf771805e: 580/0 workspace lib tests + clippy clean) - see MR !585 opened (2026-07-23 evening). In review scope: the 2026-07-23 SHORTerror.codedecision (commita58df341:Enum::Variantonerror.codeonly) - see Short error.code decision (2026-07-23) - the 2026-07-24 Caused-byerror.stackrenderer (commit1f9cd611: custom “Caused by” chain renderer replaces the crate’s Debug tree, REVISES locked decision 3) - see error.stack Caused-by renderer decision (2026-07-24) - and the 2026-07-24 error-authoring DX OVERHAUL (commitsf5beabd4report vocabulary /4e519269shorterror.detailscodes /f771805enative report signatures +MandoReportnewtype) - see Error-authoring DX overhaul (2026-07-24). Execution history: Phase 3 executed (2026-07-22 evening through 2026-07-23). Worktree.worktrees/BE-3657-phase3. Specs:docs/superpowers/specs/2026-07-15-error-stack-adoption-design.md(phases 1+2, includes the flipped template + annotation direction) +docs/superpowers/specs/2026-07-22-be3657-phase3-adapters-design.md(phase 3; now records the walled-towers formal exception) +docs/superpowers/specs/2026-07-24-be3657-report-derive-design.md(next MR:derive(MandoReport)From-impl generation). Plans:docs/superpowers/plans/2026-07-15-be3657-error-stack-phase1.md+2026-07-16-be3657-error-stack-phase2.md+2026-07-22-be3657-error-stack-phase3.md. All untracked, never commit. Remaining: the spec’dderive(MandoReport)MR (next up), phase 4 (#[error_meta(...)]annotations, Andras’s own work) + the follow-up ticket bundle in the execution section. Phase 3 telemetry VERIFIED LOCALLY end to end 2026-07-23 from the real runtime (binaryb394e0d4, a rebase-descendant; the short-code commita58df341and the Caused-by-renderer commit1f9cd611also verified live) and the DX batch is verified live 2026-07-24 through a CONTINUOUS forwarder to Datadog (env:dev-local-levander, ~5s latency; complete field contract confirmed: code short, errors full, details codes short per level, fingerprint full, Caused-by stack, nested http on 5xx paths) - see Local end-to-end verification (2026-07-23) + Local live stream operational (2026-07-24); host-process run recipe: mando-local-host-run-recipe-2026-07-23.
Locked decisions (2026-07-15/16, Andras)
- Full
error_stack(=0.8.0) adoption over an ErrorCode-derive macro. Guiding principle: “leave what we can to error_stack” - mando contributes ONLY:- the
ErrorCodeattachment (full-path code captured statically in aCodedExthelper at eachchange_context/newsite), - extraction functions, and
- an
error!(report = ...)macro arm. Everything else (chaining, source walking, rendering) is the crate’s job. Amended 2026-07-24: the authoring vocabulary changed -CodedExt/new_coded/change_context_codedare DELETED workspace-wide, replaced byToReport::into_report/Result::reported()/.wrap()/.attr(k, v); the principle itself stands - see Error-authoring DX overhaul (2026-07-24).
- the
- Field contract (single outermost identity across all three):
error.code==error.kind==error.type== the outermost full-path code.error.fingerprintuses the full-path code.error.errors= array of codes.error.details= array of{code, file, line, message}objects.- Amended 2026-07-23:
error.codealone now carries the SHORT readable form (Enum::Variant; bare struct name for variantless types);kind/type/errors/detailscodes/fingerprintstay full module path - see Short error.code decision (2026-07-23). - Amended again 2026-07-24:
error.details[].codejoins the short form (the details panel is the human panel; each entry’sfile:linecarries the precision);kind/type/errors/fingerprintstay full path - see Error-authoring DX overhaul (2026-07-24).
- Tree rendering = the crate’s own output, with codes attached as sub-lines (attachment style), NOT a prefix style. Accepted trade-off. REVISED 2026-07-24: after seeing the tree in the real Datadog UI, the crate’s Debug tree is dropped for a custom prefix-style “Caused by” chain renderer - see error.stack Caused-by renderer decision (2026-07-24).
- Boundary message template FLIPPED to STATIC-FIRST on 2026-07-16:
"{static}: {outermost}[: {chain}]". Supersedes the earlier outermost-first order ("{outermost}: {static}: {chain}") recorded on 07-15/16; the flip is in the authoritative spec and deployed in the dev prototype build. - Phase-2+ annotation direction (Andras’s own work once the phase-1 plumbing exists, must not be precluded by phase 1): per-variant
#[error_meta(event_type, resolution step, business_message, description)]feedingerror.detailsenrichment plus resolution-doc codegen. error_stack attachments are open-ended, so phase 1 does not block this. - Bodies never on spans (shared with BE-3656): request/response bodies attach only via error events, a deliberate deviation from the
logs-and-apm.mdspec table, STILL to be raised with the team. error.errors/error.detailsexcluded from span tags: the arrays live on the log side only (Datadog flattens array span attributes tokey.0/key.1, breaking facets); spans keep the scalarerror.code.- First-error-wins span enrichment guard (from BE-3656): a marker extension prevents later errors overwriting span error tags, since
set_attributeappends under the 128-attr SDK cap. - HTTP fields nested as
http.request.*/http.response.*(axum semconv-aligned; collision investigated, none).
Build state (2026-07-22)
Phases 1+2 are done and pushed. Morning state:
- 13 commits @
2baea68aatop the BE-3656 head8f50ef2a(feature/BE-3657on origin, ls-remote verified). Contains BOTH phases; suggested MR reflects that. - Rebased 2026-07-21. During the rebase, 3 develop-inherited bare-code fallback tests were aligned to full-path expectations and squashed into the commit “feat: use full module path in error code”.
- Gates green via the workspace build graph (mando_core 94/0 through the workspace). Standalone
cargo build -p mando_corefails on a PRE-EXISTING uuid/serde compile issue inherited from the BE-3643 merge (bf953ec8), not caused by this branch. - Later phases remain: phase 3 (planned 2026-07-22 evening as plumbing + adapters, superseding the earlier “workspace breadth” framing - see Phase 3 planned (2026-07-22 evening); the py-mando formatter parse-hook port and the tuple/transparent restructuring are IN it) and phase 4 (the
#[error_meta(...)]annotations, Andras’s own work).
Afternoon 2026-07-22: rebased onto develop after !578 merged
- MR !578 merged (squash
46a9be1b, merge4a0d297f= new develop tip), so the pendingrebase --onto develophappened: all 13 commits replayed zero-conflict, head2baea68abecame22ccae93, patch-id verified byte-identical to the reviewed content. - Branch CI pipeline
2696324182on22ccae93: success. - Then commit
ab8cfd11“fix: read dd service name from env” was appended (Andras asked to sneak it into the upcoming MR), making the branch 14 commits @ab8cfd11, pushed and ls-remote verified. Details in DD_SERVICE env fix (commit ab8cfd11, 2026-07-22). - Local gates green after the append: clippy clean, 516 tests passed / 0 failed at lib scope (
--lib= CI parity, see mando-ci-lib-only-test-gate-2026-07-22). - MR ceremony: nothing left but Andras’s explicit yes.
Phase 3 planned (2026-07-22 evening)
Phase 3 design approved by Andras 2026-07-22 (evening session). Kept as the planning record; execution ran the same evening through 2026-07-23, see Phase 3 executed (2026-07-22 evening through 2026-07-23).
Artifacts (untracked, never commit): spec docs/superpowers/specs/2026-07-22-be3657-phase3-adapters-design.md; plan docs/superpowers/plans/2026-07-22-be3657-error-stack-phase3.md (15 tasks).
Locked phase 3 decisions
- Scope = “plumbing + adapters”. Rejected alternatives: plumbing-only; full workspace breadth.
- Mechanics = seam replication per the Volue pilot. Rejected alternatives: native
Reportsignatures; boundary-only wrapping. Pilot artifacts already on the base branch:retrieve_ts_data_reported,send_request_reported,volue_error_from_reportinadapter/volue/ems/ts_data_retrieve.rs. REVISED 2026-07-24: the seam pattern was eliminated on the same branch in favor of the once-rejected native report signatures, made viable by theMandoReportnewtype’s?auto-lift (commitf771805e) - see Error-authoring DX overhaul (2026-07-24). - Ticket = keep riding BE-3657: branch
feature/BE-3657-phase3offdevelopAFTER !580 merges; one phase = one MR.
Conformance pass against the team spec
The design was tightened against Balazs’s team spec (architecture-design repo, doc/content/doc/ops/logs-and-apm.md, fetched from main 2026-07-22):
- Line 157 is absolute: ALL tuple/transparent variants in migrated enums get restructured.
error.detailsgains the spec’sattributesfield (lines 150-155) via a new mando-coreErrorAttrattachment + extraction; this lands BEFORE any adapter migrates.- Line 159 http roll-up: implemented as an outermost-wins pick of the report’s
HttpContextattachment;with_reportfillshttp_contextwhen unset. The spec’s “force with a macro” wording is interpreted positionally and goes on the pending team-sentences list. - Auth provider migrations carry a hard sensitive-data rule (lines 121-124): no bodies, no tokens; method/url/status only.
Adapter inventory and order (grounded on the branch)
- Auth providers FIRST:
alpiq/authentication+ the Volue EMS/ATP auth providers (sensitive-data rule above applies). volue/emscompletion: spot + send join thets_data_retrievepilot.volue/atp.alpiq/ebs: SMB, so NO http roll-up; share/pathErrorAttrinstead.alpiqmdr / opl / data_platform / et_3000.fingrid.alpiq/metis.mandogroup: rest / algo / simulator / ms_teams / microsoft.alpiq/position_manager.
Flagged defaults (Andras can veto before execution)
Two vetoable defaults baked into the plan
adapter/mando/archiver(+ its postgres submodule) DEFERRED to the repos phase: data-plane, separate CI harness, no step boundary.- The startup mock deep error gets PORTED to mainline mando-bess (it exists only on the throwaway dev-deploy branch) behind
MANDO_DEBUG_MOCK_ERROR, default-off; one IaC dev env line at deploy time; droppable if the team objects.
New mando-core surface (phase 3 task 1)
pub struct ErrorAttr(pub String, pub serde_json::Value)attachment.error_attr_value: one-level-deeper rendering for error-typed attributes.report_first_attachment: outermost-wins generic pick.report_detailsentries gainattributes.- A shared
error_json_field_valuefn unifies theerror.errors/error.detailsJSON parsing acrossdd_formatterand both python package log formatters (py-mando/src/log_formatter.rs,py-mando-simulation/src/log_formatter.rs).
Phase 3 executed (2026-07-22 evening through 2026-07-23)
Phase 3 ran subagent-driven on branch feature/BE-3657-phase3: 14 tasks + a final whole-branch review, every task under an adversarial spec + quality review. The branch was initially stacked on the in-review !580 head ab8cfd11; after !580 merged, rebase --onto develop replayed with zero conflicts, patch-id byte-identical. 20 commits at execution end, review HEAD e0184bc3, PUSHED (ls-remote verified). Update 2026-07-23 evening: 22 commits @ a58df341, IN REVIEW as MR !585 - see MR !585 opened (2026-07-23 evening). Neither flagged default was vetoed: the archiver seam migration stayed deferred per the plan (the transparent sweep still covered adapter/mando/archiver), and the mock port shipped env-gated.
Delivered
- mando-core surface:
ErrorAttrattachment + per-levelattributesonerror.detailsentries;error_attr_valueone-level nesting for error-typed attribute values;report_first_attachmentoutermost-wins generic pick;with_reporthttp roll-up (fillshttp_contextfrom the report’s outermostHttpContextwhen unset). - ALL in-scope adapter groups migrated with
*_reportedseams and exhaustive no-wildcard*_error_from_reportconversions; full tuple/transparent elimination (final sweep: zeroerror(transparent)left inmando-lib/src/adapter, archiver included). - Auth providers migrated first under the hard no-bodies/no-tokens rule (method/url/status only); sentinel leak tests everywhere.
- PyO3
map_report_error: forward plumbing only; the wiring is grep-proven unreachable this phase. - Shared
error_json_field_valuehook unifying theerror.errors/error.detailsJSON parse acrossdd_formatter,py-mando/src/log_formatter.rs, andpy-mando-simulation/src/log_formatter.rs. - Env-gated report-backed mock error:
MANDO_DEBUG_MOCK_ERROR, default off; replaces the dev-deploy branch’s hardcoded-JSON mock (supersedes the “mock still on the typed arm” warning in the dev prototype section, effective once phase 3 merges).
Key architectural finding: walled service towers (formal spec exception)
Only volue/ems carries Reports to the step boundary in production
The
volue/emsservice towers now useError = Report<VolueEmsError>. The other SEVEN adapter groups are WALLED: their service towers fix flat error types, and thepub(crate)seam visibility makes cross-crate wiring structurally impossible, so their rich chains flatten at the legacy wrappers until a services-phase tower restructure. Every wall was independently verified genuine during review. The phase 3 spec records this as a formal exception; future phases inherit it as the starting map.Update 2026-07-24: the
*_reportedseam MECHANICS are gone (commitf771805e): adapters expose ONE fn per operation returningMandoResult, and the flattening moved to the tower edges (~50 consumer edits). The walls themselves stand - flat-typed towers still flatten rich chains - but the dual-fn seam pattern no longer exists. See Error-authoring DX overhaul (2026-07-24).
Process notes
- 3 of the 14 tasks needed fix waves, all dead-code deletions per review theme 2 (the “delete unused code” precedent): opl deleted dead order code; et3000 and position_manager dead variants deleted on review.
- The final whole-branch review caught a Critical no task gate could see: the
mando-simulatorCONSUMER crate still had 2 tuple-syntax construction sites. Task gates compiled-p mando_libonly, never the consumer crate; workspace-wide clippy is the only gate that catches cross-crate fallout. Companion lesson recorded in mando-ci-lib-only-test-gate-2026-07-22. - The final fix wave also de-transparented 4 remaining variants after the reviewer ruled spec line 157 absolute: transparent named-source singles duplicate the inner level’s message in
error.details.
Final gates at e0184bc3
- Workspace
cargo clippy --release --all-features: exit 0. - 574 lib tests passed / 0 failed (
--lib= CI parity, see mando-ci-lib-only-test-gate-2026-07-22). - Transparent sweep: zero hits.
- No
Cargo.lockchurn. - Diff vs develop: 61 files, +4390/-1482.
- py-mando / py-mando-simulation pytest DEFERRED to CI (no local venv; Nexus is VPN-gated).
Follow-up ticket bundle (final reviewer’s triage)
- Auth-provider PRE-EXISTING
info!/debug!body logs (a real leak surface, out of phase scope). ErrorAttrids on non-success terminals only.- py delegation tests CI-only.
data_platformAWS env test panic-safety.- opl
clear_strategiesmissing portfolio attr. map_errclosure-form drift.
Plus the standing items: the flight_end_to_end one-liner import fix (mando-ci-lib-only-test-gate-2026-07-22) and the mando-core standalone uuid/serde build fix.
Branch state
develop moved again after the rebase: 6fae64a5, the merge of feature/BE-2262-bess-am-poc (BESS AM (BE-2262) - mando-bess-am). The phase 3 branch is 1 merge behind; a pre-MR rebase is possible when the MR gets the go.
Update 2026-07-23 evening: resolved. The branch went to review as MR !585 at head a58df341 (22 commits, includes the short error.code commit) with no conflicts against develop and a green branch pipeline on the same sha - see MR !585 opened (2026-07-23 evening).
Short error.code decision (2026-07-23)
New decision by Andras (2026-07-23), amending field-contract decision 2 in the locked decisions above: error.code now carries the SHORT readable form, honoring the team spec’s line-100 mobile-readability intent: Enum::Variant (e.g. VolueEmsError::AuthenticationError), bare struct name for variantless types. error.kind, error.type, error.errors, error.details codes, and error.fingerprint ALL stay full module path.
Implementation (commit a58df341, the MR !585 head):
short_error_coderesolver in mando-core; PascalCase-segment heuristic, valid because house naming keeps type/variant segments PascalCase and module segments snake_case.- Applied at the 4 macro emission sites ONLY; span/boundary layers inherit the short form as passthrough.
- The short/full split is pinned by test
kind_type_fingerprint_stay_full_while_code_goes_short; 577/0 workspace lib tests at the commit. - Verified LIVE in the local host-process run and forwarded to Datadog under
env:dev-local-levander.
Consequences:
- The “mobile readability rationale is dead” pending team sentence is WITHDRAWN: conformance with the team spec is restored.
- The DD cutover impact for
@error.code-keyed assets shrinks: the short form resembles the legacy codes (cutover callout below updated). @error.sourceis a RETIRED field: renamed toerror.messagein the redesign, pinned by a mando-core regression test. Any facet or monitor still keyed on it must be deleted or rekeyed onto@error.message/@error.errors. Andras hit exactly this today: an emptyerror.sourceconsumer, since fixed by him.
Update 2026-07-24: extended one level deeper - error.details[].code also goes short (commit 4e519269); errors/kind/type/fingerprint stay full path. See Error-authoring DX overhaul (2026-07-24).
MR !585 opened (2026-07-23 evening)
Phase 3 went to review as MR !585 “feat: adopt error stack across all adapters”, description Closes BE-3657 (GitLab default per house ceremony), feature/BE-3657-phase3 → develop at head a58df341 (22 commits). No conflicts against develop; the branch pipeline is green on the same sha. Reviewers: krisztian.fekete1, gabor.nagy6, balint.budavoelgyi, jozsef.nagy1. The head commit is the short error.code implementation above, so it is in review scope.
Update 2026-07-24: the branch/MR head moved to 1f9cd611 “feat: render error stack as caused by chain” (23 commits, pushed + ls-remote verified) - the Caused-by error.stack renderer below rides the same review.
Update 2026-07-24 (later): the head moved again to f771805e “refactor: collapse adapter seams into native report signatures” (~27 commits, pushed) - the whole error-authoring DX overhaul (report vocabulary + short details codes + native signatures) rides the same review. See Error-authoring DX overhaul (2026-07-24).
Status 2026-07-24 (verified): MR !585 (head f771805e) is still OPEN with zero review comments; has_conflicts is false against develop even after develop took the BE-3685 merge daf6b6df; the head pipeline is success; the only unmet merge gate is not_approved.
error.stack Caused-by renderer decision (2026-07-24)
New decision by Andras (2026-07-24), REVISING locked decision 3: error.stack rendering switches from the error-stack crate’s Debug tree to a custom “Caused by” chain renderer. Trigger: Datadog collapses newlines in several views, and the crate’s ASCII connectors (|-, lone |, |->) turned into unreadable pipe soup in the real DD UI. The prefix-style renderer the original trade-off rejected is now adopted.
New format per level:
- Head line
{short code}: {message}- first level bare, every subsequent level prefixedCaused by:. - Location line
at file:line:column. - One
with key = valueline perErrorAttr. - Opaque attachments are no longer mentioned.
Implementation (commit 1f9cd611 on feature/BE-3657-phase3, the new MR !585 head):
render_stack_treerewritten to walk frames the same wayreport_detailsdoes.- The crate debug-hook plumbing (
install_debug_hook,Charset/ColorMode,HOOK_INIT) deleted as dead. - 579/0 workspace lib tests, including an exact multiline golden pin of the new format.
- Verified LIVE locally and forwarded to Datadog: the 2026-07-24 events under
env:dev-local-levandercarry the new stacks AND the shorterror.codetogether.
Datadog research backing the decision
Findings from the Datadog docs, worth keeping:
- Error Tracking for logs requires error-level status +
service+ (error.kindOR a validerror.stack). mando always emitserror.kind, so the stack format never gates tracking/grouping for us. - DD’s “valid stack trace” bar is at least two lines with one meaningful frame carrying a FUNCTION NAME plus a filename. error_stack
Locations carry no function names, so mando stacks can never be frame-parsed by DD regardless of format. - The
sourcetag drives auto-parsing only for conventional language stacks; log remappers relocate attributes but do not parse.
Conclusion recorded: mando’s error.stack is purely presentational - optimize for human readability and graceful newline-collapse, which the Caused by: separators survive.
Error-authoring DX overhaul (2026-07-24)
Decision series by Andras (2026-07-24). North star: error reporting must not break Rust’s natural flow - ? is the API. Three commits landed on feature/BE-3657-phase3 (head now f771805e, ~27 commits, pushed, riding MR !585), and the next MR - a derive(MandoReport) proc macro - is designed and spec’d.
Report vocabulary (commit f5beabd4)
refactor: adopt report vocabulary for error authoring:
new_coded/CodedExt/change_context_codedDELETED workspace-wide.- Replaced by:
ToReport::into_report(error-stack 0.8 already exports anIntoReporttrait, hence theToReporttrait name; the METHOD name stays unambiguous),Result::reported(),.wrap(), and.attr(k, v)sugar. flatten_reportfor Clone enums: 6 conversion fns became one-liners.- 307 sites swept across 37 files.
Bonus find: 20 degraded-location sites fixed for free
20
map_err(new_coded)FN-POINTER sites existed - the exact#[track_caller]-losing form - meaning those sites had degraded (non-call-site) locations until this commit swept them into the new vocabulary.
Short error.details codes (commit 4e519269)
feat: shorten error detail codes - extends the 2026-07-23 short error.code decision one level deeper:
error.details[].codenow carries the SHORT form: the details panel is the human panel, and each entry’sfile:linealready carries the precision.error.errors,error.kind,error.type,error.fingerprintstay full module path.- The short/full split is pinned by a test asserting both forms within ONE emission.
Native report signatures (commit f771805e)
refactor: collapse adapter seams into native report signatures - REVISES phase 3 locked decision 2. Andras’s verdict on the dual *_reported seam pattern: “not fit for a codebase this big”.
- ~35 flat twins deleted; adapters now expose ONE fn per operation, returning
MandoResult. - New bare newtype in mando-core:
MandoReport<C>(Report<C>)withDeref,as_report,into_inner, delegatingDisplay/Debug, astd::error::Errorimpl withsource()=None, and a plainFrom<Report<C>>enabling?auto-lift; plus aMandoResult<T, E>alias. - ~50 consumer edits: flattening moved to the tower edges.
- The
volue/emstowers keepError = Report<VolueEmsError>internally, bridging withinto_innerat 5 sites. - 5 dead conversion fns deleted on the branch’s own theme-2 precedent.
- Gates: 580/0 workspace lib tests, clippy clean.
Next MR designed: derive(MandoReport) (spec’d 2026-07-24)
Spec: docs/superpowers/specs/2026-07-24-be3657-report-derive-design.md (untracked, never commit). Goal: #[derive(MandoReport)] generating From impls so error handling becomes plain ? everywhere.
#[report(from(SrcType))]: display-mapping DEFAULT (preserves the Clone/message architecture).#[report(wraps(Inner, message = "..."))]: chain levels.MandoErrormarker trait bounds at boundaries + a clippy ban onReport::new.
Spike results (scratchpad/spike-trackcaller, rustc 1.89.0, error-stack 0.8.0):
- Direct
Fromimpls onReport<LocalEnum>are ORPHAN-BLOCKED (E0117): the naive design is dead on arrival. - Through the local newtype,
#[track_caller]propagates?-site locations EXACTLY; without the attribute, locations degrade to thefrombody. - Andras had offered to sacrifice site tracking for the ergonomics; the spike proved no sacrifice is needed.
The spec grounding pass caught two design regressions BEFORE implementation:
from(Src)must DISPLAY-MAP: carrying source fields would kill the Clone/flatten architecture.wraps()needs an explicit static message for struct variants.
Datadog operational gotchas (2026-07-24)
- Facet queries with
::in the value MUST be quoted:@error.code:"VolueEmsError::ApiError". Unquoted, the query silently matches nothing. - Stale server held port 8081: the instance left running from the 2026-07-23 session survived and kept the port, so a new launch died with AddrInUse WHILE the old build’s old-format emissions kept flowing - a confusing mixed signal when iterating on log formats. Diagnose with lsof on the port and inspect the holder’s binary path.
Local live stream operational (2026-07-24)
- Server on 8081 (build carrying the DX batch); a CONTINUOUS forwarder now ships error/warning lines to Datadog EU within ~5s under
env:dev-local-levander. MANDO_ENVIRONMENT=dev-local-levanderis now set AT SOURCE, so the emittedddtagsare correct without the forward-time rewrite from the 2026-07-23 epilogue (the epilogue).- Complete field contract verified LIVE:
error.codeshort,error.errorsfull,error.detailscodes short per level,error.fingerprintfull, Caused-byerror.stack, nestedhttp.request/http.responseblocks on 5xx paths.
Local end-to-end verification (2026-07-23)
Full LOCAL end-to-end test of the phase 3 error_stack telemetry. Every emission below came from the real runtime, not tests.
Setup: phase-3 branch binary (feature/BE-3657-phase3 @ b394e0d4, rebase-descendant of the reviewed e0184bc3, built in .worktrees/BE-3657-phase3) run as a HOST process against the mando-cli local stack (mando up --datadog -p infra: postgres + wiremock + local-dd-agent). Env assembled from .cargo/config.toml.example with overrides; env file kept at the session scratchpad (mando-p3.env). Full run recipe + friction log: mando-local-host-run-recipe-2026-07-23.
Verified emissions
- Boot mock (
MANDO_DEBUG_MOCK_ERROR=true): 4-level report;error.errorsarray of 4 full-path codes;error.detailsentries with realfile:lineANDattributes(data_groupon the top level);error.stackbox tree including theErrorAttrsub-line and “1 additional opaque attachment” (theHttpContext);code==kind==typefull path; root-causeerror.message; trace correlation. (Both the box-tree stack shape and the opaque-attachment mention here are superseded 2026-07-24 - see error.stack Caused-by renderer decision (2026-07-24).) - Volue EMS spot step (404 from unstubbed wiremock): single-level coded emission with fingerprint
code|step_path(data_update.load_battery_momentary_data),file:linespot_data.rs:79, static-first boundary message. - Volue EMS retrieve step: TWO-LEVEL chain in production:
VolueEmsError::AuthenticationError+VolueEmsAuthenticationProviderError::FailedRequest; details entries atems.rs:217andems_auth_provider.rs:155; chain-carrying boundary messageFinished running step with errors: authentication error: '...': failed request (status: 404). This is the auth level the final-review fix wave added to the pilot retrieve path, confirmed live. - Metis merit order step: full-path
error.code(MetisError::AuthenticationError) with NOerror.errorsarray - the documented walled-tower flattening (Key architectural finding: walled service towers (formal spec exception)), the mixed world working as designed. dd_formatter: renderserror.errors/error.detailsas REAL nested JSON in the log lines (the sharederror_json_field_valueparse hook).
Datadog forwarding
12 error lines forwarded to Datadog EU via DIRECT INTAKE under env:dev-local-levander service:bess-os-service-mando. Host processes are not tailed by the local dd agent (its include filter scopes to mando-* containers, see mando-cli-datadog-local-logs-2026-07-22), so direct intake is the host-process path.
Session end state
Server left RUNNING at session end
localhost:8081, health 200, execution history queryable. Stack stops with
mando down; the host process withpkill mando_bess.Update 2026-07-24: this survived instance later held port 8081 and made a fresh launch die with AddrInUse while its old-format emissions kept flowing - see Datadog operational gotchas (2026-07-24).
DD_SERVICE env fix (commit ab8cfd11, 2026-07-22)
Rider commit on this branch replacing the hardcoded Datadog service name "bess-os-service-mando" in mando-lib with a DD_SERVICE env read, keeping the hardcoded value as fallback (also on empty string). It closes the mando-side root cause of the FR unsuffixed-service-name issue (fr-region-missing-datadog-logs-2026-07-21).
- Implementation:
resolve_dd_service()+ aOnceLock-cacheddd_service()accessor inmando-lib/src/app/mod.rs(appfeature gated). - All 6 hardcode sites routed through it: the OTel Resource service name (
get_tagging), theddtagsindd_formatter.rs, and 4 rawglobal::meter("bess-os-service-mando")literals inservice_base.rs(3x) andutil/http_client_trace.rsthat had bypassed the old const entirely. - Inline test covers default / empty-string / override behavior.
- Deployment-neutral on the main stack: optimization-universe-iac
terraform/bess_os_ecs.tfline 218 ALREADY setsDD_SERVICE = "bess-os-service-mando"on the mando container (algo containers and lambdas set their own names), so the code now honors what IaC says instead of ignoring it. The FR stack’s-fr-suffixed value starts winning once a build carrying this fix deploys there.
Dev prototype verification (build b7d15e2c)
Dev runs the throwaway build 1.16.1-feat.2682437891.b7d15e2c (branch feature/BE-3482-dev-deploy head b7d15e2c, static-first template merge; IaC pin applied 2026-07-16, Apply complete; NEVER merge, delete after the train lands). Stack = BE-3541 + BE-3656 + BE-3657 phases 1+2 + full-path codes + startup mock deep error + nested http.request/http.response + static-first boundary message + flow.step.system.
Verified in Datadog:
- Nested
error.details: 4-level chain with realfile:linevalues. error.errorsarray queries: element match, wildcard, and negation all work.- Nested http shape:
http.{request{method,url,version,body,content_length}, response{status_code,body,content_length}}. - Trace correlation confirmed (closes the “verify
dd.trace_idcorrelation” deferral); sample trace0dc8d42739a08ac0805808b2d14b6644.
Mock still on the typed arm
The startup mock deep error fires once per task boot but still uses the typed
error!arm with hardcoded JSON, NOT thereportarm. Offer to upgrade it stands. Update 2026-07-23: superseded on the phase 3 branch - the mock is ported to mainline mando-bess as report-backed behindMANDO_DEBUG_MOCK_ERROR(default off). Effective once phase 3 merges; the throwaway dev-deploy branch keeps the old hardcoded-JSON form until then.
Pending DD cutover at BE-3657 merge
Datadog assets must be cut over when this deploys
All
error.code/ fingerprint values change format (short codes become full module paths) at the BE-3657 deploy. At merge time:
- Create facets:
@error.fingerprint,@error.errors,@error.details.code.- Inventory and update monitors keyed on SHORT codes, on flat
http.*fields, and on the exact message"Failed running step".Status 2026-07-23: !580 (phases 1+2) is MERGED, so this checklist becomes actionable with the first develop-based dev deploy carrying it.
Update 2026-07-23 evening: the short
error.codedecision (commita58df341, in review with MR !585) shrinks the@error.codepart of item 2:error.codestaysEnum::Variantand closely resembles the legacy codes, so the format break narrows toerror.kind/error.type,error.fingerprint, and the new array fields. NEW item 3:@error.sourceis RETIRED (renamed toerror.messagein the redesign, pinned by a mando-core regression test) - delete or rekey any facet/monitor still on it to@error.message/@error.errors; one emptyerror.sourceconsumer was found and fixed by Andras 2026-07-23.Update 2026-07-24:
error.details[].codealso goes SHORT (commit4e519269), so the@error.details.codefacet keeps a readable short form; the full-path break remains onerror.kind/error.type/error.fingerprint/error.errors. QUERY RULE for the new codes: facet values containing::MUST be quoted (@error.code:"VolueEmsError::ApiError") - unquoted queries silently match nothing.
Relationship to prior direction
Supersedes the ErrorCode-derive / mando-lib-macro direction that only ever lived in worktree experiments (see Mando AGENTS.md Master Guide - there is NO ErrorCode derive on develop; error.kind/error.code are extracted at runtime by mando_core::error! from the Debug repr today). BE-3657 replaces that runtime Debug-repr extraction with static full-path codes captured at the error-construction site - originally via CodedExt, since 2026-07-24 via the ToReport report vocabulary (CodedExt is deleted, see Error-authoring DX overhaul (2026-07-24)).
Related
- BE-3656 APM Span Enrichment - immediate predecessor, merged as !578 (merge
4a0d297f); BE-3657 now sits directly on that merge, and the shared dev-deploy prototype validates the BE-3657 field shapes - fr-region-missing-datadog-logs-2026-07-21 - the FR unsuffixed-service-name issue closed by the
ab8cfd11DD_SERVICE env fix on this branch - mando-ci-lib-only-test-gate-2026-07-22 - CI gate parity discovery made while running this branch’s gates
- BE-3541 Single Error Emission - single boundary emission, merged as !571; precondition for the clean stacking
- BE-3482 Datadog Logs and APM Conformance - the conformance program this feeds
- BE-3613 Algo Services py-mando Conformance - sibling workstream moving the algo services onto the conformant wheel
- BE-3117 Per-Flow Error Context Store - the error-handling redesign lineage
- mando-lib - crate hosting the migrated adapter/service layers; the report vocabulary (
ToReport,MandoReportnewtype) + extraction fns + macro arm live in mando-core - Agent Context