Repository navigation
BEX tracing: reconcile event identity (PR #3730 + review fixes) with the profiling ring (PR #3733) - #3740
Conversation
…eam (PR1) First slice of the BEX tracing M2-M4 plan: the prof module skeleton in bex_events, environment knob parsing (BAML_PROFILE, BAML_RING_SEG_BYTES, BAML_RING_MAX_OVERFLOW_BYTES, BAML_RING_FREELIST_CAP, BAML_PROF_WAKE_INTERVAL_MS), the minstant-backed profiling clock with a wasm32 stub, and the fixed-layout little-endian ring records (v2 spec section 4.1) with panic-free decoding and round-trip tests. New workspace deps: minstant (calibrated TSC clock), crossbeam-utils (CachePadded, for the ring in the next PR), loom (cfg(loom)-gated dev-only model checking, also next PR; cfg registered in check-cfg). PR1 gate (clock micro-bench, 20M reads, this hardware): prof::clock::now_ns 8.52 ns/read minstant::Instant::now 8.05 ns/read std::time::Instant::now 20.10 ns/read Under the <=10 ns/read assumption behind the ~25 ns/call-pair budget.
…m+miri gates (PR2) The keystone of the BEX tracing M2-M4 plan: the lock-free profiling ring in bex_events::prof, implementing the locked design decisions D1-D7. - ring.rs: per-(engine, os-thread) segmented SPSC ring. Lock-free push (memcpy + one Release store per record), lossless-by-growth (linked segments; never drops, never blocks), D1 drain hand-off (re-load commit_len after observing the link), D2 producer-owned recycle reset, D3 CachePadded producer/consumer/shared field groups, D7 free-list cap with approximate length. Hitting BAML_RING_MAX_OVERFLOW_BYTES is a hard process error with a clear message (D6), never a silent drop. - registry.rs: append-only push-only Treiber registry (no pop => no ABA), D5b orphan -> drain-to-empty -> pool -> CAS-claim lifecycle, TLS-destructor orphan trigger, and the D5a resume-site lookup (ring_for_engine) for the PR4 engine integration. - wake.rs: D4 parked-flag + park_timeout protocol; the producer's wake path is a Relaxed load + lock-free OnceLock get + unpark, never a block. The residual lost-wakeup race is documented benign, bounded by the timeout. - sync.rs: loom/std shims so the identical protocol code is model-checked and shipped. The cfg is baml_loom (not the conventional loom) because a global --cfg loom half-activates loom support in third-party deps (boxcar via salsa) that need their own loom feature to compile. Verification (the PR2 gate, all green locally): - loom: the 5-test checklist from plan section 3.7 (D1 hand-off, D2 recycle, free-list SPSC, orphan/pool/claim incl. engine_id publication and double-claim exclusion, registry concurrent acquire + pooled reuse), preemption bound 3. - miri (Stacked Borrows): same scenarios on real threads, plus the stress tests, with -Zmiri-ignore-leaks (rings are &'static by design) and -Zmiri-disable-isolation (park_timeout/sleep). - stress: 2-producer flood of 400k real CallFunction records through 256-byte segments (constant growth + recycling) with the real wake/park consumer loop; exact per-engine sequence equality; idle memory bound asserted. Orphan-churn and global-TLS reuse tests. - miri runs use a clock stub: minstant's #[ctor] probes the TSC with _rdtsc, which miri cannot execute. CI: new prof-concurrency job (loom + scoped miri) gated on bex_events changes, wired into the failure alert; deliberately narrower than the disabled whole-crate miri-tests job. No PR4 (VM/engine) integration in this change, per the plan's gate ordering.
Findings from a 33-agent adversarial review of the PR1+PR2 diff (5 lenses: plan conformance, memory ordering, unsafe/aliasing, lifecycle races, tests/CI; every finding cross-examined by 2 independent verifiers): - RingHandle::push was a safe pub API whose record-length precondition was debug_assert-only: a >seg_bytes record in a release build would memcpy past the fresh segment's buffer (UB), reachable from safe downstream code (confirmed independently by all five lenses). Fixed twice over: Ring::push now enforces the bound with a release-mode assert placed in the slow path (an oversized record always takes the slow path, so the hot path stays check-free), and RingHandle::push is now an unsafe fn - the not-after-TLS-teardown half of its contract (stale Copy handle in a later-running TLS destructor could race a new claimant) is not type-enforceable, so it must be a stated obligation. - orphan()/mark_pooled() gained debug prior-state asserts (Active -> Orphaned -> Pooled), cheap off-hot-path tripwires for the PR4 integration. - New loom+std scenario sweep_races_orphan: Registry::sweep racing the producer's orphan store (stale-Active and orphan-mid-sweep observations must be benign and self-correcting). - Behavioral leak checks: live_bytes() == 0 asserted after Ring/Registry teardown - validates the segment-reclaim walks that miri cannot see under -Zmiri-ignore-leaks. - New unpark_delivery test: a parked consumer must be woken by a producer segment fill well under a 10s timeout; the flood test alone (1 ms park) passes identically with a fully broken Wake. - Knob parsing now saturates numeric overflow instead of silently falling back: an over-usize::MAX BAML_RING_MAX_OVERFLOW_BYTES previously shrank to the 1 GiB default - arming the D6 abort the user was raising the cap to avoid. - CI: check_prof now also watches the workspace manifest (loom/minstant versions, check-cfg list), rust-toolchain.toml, and ci.yaml itself, so changes to the gate's own inputs re-run it. Cargo.lock deliberately excluded (churns on every bump; cargo-tests covers the std halves). Gates re-run green: loom 9/9 scenarios, miri 29/29, cargo test 49/49, clippy clean.
…les (PR3) PR3 of the BEX tracing M2-M4 plan: the consumer half of the pipeline. - prof/proto/bamlprof.proto + build.rs (vendored protoc, prost-build into OUT_DIR, bridge_ctypes pattern): DiskEventV1 oneof + EventFileHeaderV1 with process_id, engine_id, program_id placeholder (M0 coordination), u128 wall anchor, and the per-run function metadata table. - consumer.rs: the bex-prof-consumer std::thread, lazily spawned on first ring acquisition when BAML_PROFILE is on. Sweep loop per plan 3.4 + 3.6 (park/recheck/timeout), per-engine BufWriter file writers, 1s process heartbeats, transcode (pure encoding change; 0=none ring conventions become optional fields). Never panics: IO failures disable that engine's file with a stderr report; a corrupt committed range reports once and drops the rest of the range. Never touches GC heap or permits. - prof::flush_and_join(timeout): drain-everything-committed + file flush with ack (the bex_events_native flush pattern); consumer keeps running. Hooked at the four exit paths next to the legacy sink flushes: baml_cli run_command (both runtimes), baml_pack_host main, bridge_cffi flush_event_sink (reaches the C ABI flush_events), baml_lsp_server shutdown. No-op when profiling is off. - prof::register_engine_metadata: engines register program/function-table header info (the 2.6 interim provider's output; PR4 calls it). - file.rs: length-delimited framing writer + read_bamlprof reader (pub: used by the PR4 gates and ad-hoc tooling until the M5 renderer). - New knob: BAML_PROFILE_DIR (default .baml/profiles). PR3 gate: - e2e fake-producer test: known record sequence through a real ring (tiny segments, forced growth) into a real consumer thread; the .bamlprof parses back to the exact event sequence with the registered header. - Measured drain+transcode+write throughput: 7.5M events/s/core (prof_drain_throughput, release, this hardware) - recorded in the consumer module docs with the D6 capacity formula.
PR4 of the BEX tracing M2-M4 plan: the producer side. Per-call profiling
events now flow from the VM hot path and the engine arms into the ring.
VM (bex_vm):
- BexVm: prof_ring (D5a per-resume snapshot, never valid across .await),
prof_thread_id, call_id_counter, current_call_id, pending_sysop_call_id.
Call ids are minted unconditionally ($id semantics, M1 reads them);
only ring writes are gated.
- BytecodeFrame: call_id + parent_call_id (Vec-element cost, not Object
cost; parent stored in-frame so frameless inline natives in progress
cannot be skipped by a frame-walk restore).
- CallFunction after every bytecode frame push (shared call path, entry
points, trampolines, interrupt frames); EndFunction{Ok} at Return;
EndFunction{Error} at the bytecode unwind pop AND at the terminal
unwind (the entry frame is never popped on an unhandled throw - its
pair is closed at the ThrownUnhandled return, keeping balance).
Native/CPS frame pops emit nothing (nothing emitted on entry; plan
invariant 3).
- PR4b: sys-op calls (incl. LLM calls and host-closure calls) emit
CallFunction at the VmExecState::SysOp yield sites; the engine closes
the pair on completion. Inline natives emit Call/End pairs only when
they complete inline (Done/Error) with the true start timestamp;
YieldToCall natives stay transparent (CPS tracking is a follow-up).
Engine (bex_engine):
- engine_id minted from a process-global AtomicU64; prof_enabled
snapshot; per-engine logical-thread-id mint.
- Interim function-id provider (plan 2.6) riding the existing pre-heap
compact-encode walk: per-run 1-based ids on Function.function_id
(borsh(skip): runtime-only, pack format unchanged) + the header
function table registered with the consumer. M0 replaces this seam.
- D5a refresh at the top of every exec resume (TLS lookup, never per
push). Engine-side emissions (thread lifecycle, sys-op completion)
always re-resolve the ring via TLS - post-await the task may sit on a
different OS thread, so the VM snapshot must never be reused there.
- StartThread at root creation and at the Spawn arm (the one place the
parent thread id, spawning call id, and child name are in hand);
EndThread via a run_thread_event_loop wrapper covering every exit path
(Completed/Cancelled/Errored; Exit counts as Completed).
Gates (bex_engine/tests/prof_gate.rs, all green):
- G3 lossless: spawn-heavy + call-heavy program at the smallest legal
segment size (forced growth/recycling): exact per-function call counts
(9 work / 2700 mid / 5400 leaf / 1 main), 9 logical threads, exact
Call/End and StartThread/EndThread set balance.
- Reconstruction smoke (v2 7.2): per-thread stack discipline over
ts-ordered events + spawn-edge validity (parent thread + call exist).
- Sys-op pair test (baml.sys.sleep, async + permit release) and the
unwind test (Error ends for the whole unwound chain, Errored thread).
- G2 overhead bench (call-chain-100x10k, BAML_PROFILE=0 vs 1) measured
separately; numbers recorded in the PR5 doc sync.
PR5 of the BEX tracing M2-M4 plan. - Orphan-path soak: 64 rounds of short-lived producer threads (the blocking-pool churn pattern) against a LIVE consumer thread; asserts pooled-ring reuse keeps the registry bounded and every record reaches disk. (Back-to-back churn without idle gaps legally allocates more rings - bounded by peak concurrent un-pooled threads - covered by the no-gap variant in concurrency_tests.) - Overflow-cap abort test: subprocess asserts the D6 hard error fires with the documented message (knob name + "cannot keep up" diagnosis), not a silent drop or an opaque OOM. - Hot-path trims after the G2 measurement: exact-size record buffers (no 292-byte MAX_RECORD_LEN zeroing per emission), #[inline] on the encode path, and inline-native pairs written as a single push (one bounds check + one Release store for the pair). - Freelist cap stays at 4 (idle cost 1 MiB/ring at defaults; no measurement motivated a change). G2 numbers (call-chain-100x10k, 1M call pairs/run, medians; recorded in the design doc's new section 0.1): pre-PR baseline 74.4 ms | profiling off 80.7 ms (+8.4 percent always-on: unconditional $id mint + 16-byte-larger frames) | profiling on 143.7 ms (~63 ns/pair vs the ~25 ns budget; 2 clock reads are ~17 ns of it). Realistic workloads: method-call ~0 percent, bubble-sort +4.4 percent. The microbench overshoot and follow-up candidates are documented in bex-event-stream-design-v2.md 0.1, which also gained the measured capacity formula (7.5M ev/s/core drain), the real knob list, and the implementation-status ledger. Default stays OFF (BAML_PROFILE=1 opt-in); the default-on flip is its own one-line PR after the gates have CI history, per plan section 5.
…und 2
A 69-agent adversarial review of the PR3-PR5 diff (5 lenses, every
finding cross-examined by 2 verifiers) confirmed one critical bug class
and several lifecycle holes. All fixed here.
CRITICAL - stale D5a ring snapshot crossing awaits (SPSC two-producer
race => UB + ring corruption), two manifestations:
- inject_sysop_throw runs the VM unwinder (which emits
EndFunction{Error} through vm.prof_ring) after the sysop permit
release/reacquire awaits - the snapshot belonged to the pre-await OS
thread. Fired on every caught async sysop/host error.
- The root entry-frame CallFunction: new_root_thread snapshotted the
ring, then awaited permit acquisition (a real suspension point under
GC park contention) before set_entry_point pushed through it.
Fix: a single prof_refresh_vm_ring(vm) helper (TLS re-snapshot), called
at the loop head, after EVERY in-loop permit re-acquire (SysOp, Await,
AwaitAny), and immediately before set_entry_point in run_entry_point
and spawn_thread_inner. new_root_thread no longer snapshots at all.
Lifecycle balance:
- Root StartThread moved from new_root_thread into the
run_thread_event_loop wrapper - the same function that emits
EndThread - so early-error returns between root creation and the loop
(arg conversion, stale handles, arity errors) can no longer leak a
started-but-never-ended thread.
- Queued-then-cancelled spawns (TaskGroup ticket denied) never run the
event loop: their task body now closes the already-emitted entry
CallFunction (Error) and EndThread (Cancelled).
- ThreadOutcome::SettledChild now carries a ChildSettleKind
(Fulfilled/Cancelled/Errored): spawned children previously always
ended Completed - Cancelled/Errored were unreachable for non-roots.
- prof_emit_native_pair minted call ids only when profiling was on,
violating plan invariant 5 ($id determinism); minting is now
unconditional, only the ring write is gated.
Consumer robustness (uncertain-bucket findings adjudicated real):
- The flush drain loop is bounded (1024 sweeps): a producer outrunning
the drain could previously starve the control channel - and every
future flush ack - forever.
- report() no longer uses eprintln! (panics on closed stderr; the
consumer must never die silently); metadata lock is poison-tolerant.
- Removed the dead corrupt-flag; honest durability wording
(BufWriter::flush, not fsync); known-limitations note for the absent
engine-teardown hooks (open writers + heartbeats for process life).
Also: proto header now documents that FILE ORDER IS NOT EVENT ORDER
(sort by timestamp_ns within thread_id); record.rs call_id doc fixed to
per-logical-thread scope; the interim provider's table row builder
extracted into the single seam fn the plan asked for
(prof_function_meta_entry); stale "Remove with PR3" blanket dead_code
allows removed (two targeted accessor allows remain); prof_gate's
profile dir gained a startup nonce against pid reuse.
Re-verified: loom 9/9, miri 32/32, bex_events 53/53, prof_gate 4/4,
full bex_engine suite green, clippy clean.
… ack
Closes the three documented limitations from review round 2.
- Engine teardown: BexEngine::drop sends a non-blocking
EngineClosed(engine_id) to the consumer, which drains the engine's
remaining events (every commit happened-before the last Arc release),
fsyncs and closes its .bamlprof - freeing the fd and stopping its
heartbeats - and frees its metadata entry. Closed ids are tombstoned
so a stale record cannot silently resurrect the file (reported once,
dropped). Engine-churning hosts (LSP recompiles) no longer accumulate
open files or heartbeat work, clearing the blocker this had put on
the default-on flip. Residual cost per closed engine: one 8-byte
tombstone; rings claimed by still-live threads stay registered (idle)
until those threads die and the rings pool - bounded by peak
concurrency, per plan invariant 7.
- Torn tails: read_bamlprof now returns BamlprofContents { header,
events, truncated } - the whole-message prefix instead of rejecting
the entire file, honoring the design's partial-parseable bet (v2 core
bet 3) for crashed writers and live heartbeat appends. The gate
tests' retry-on-parse workaround is gone; unreadable headers (file
mid-creation) are skipped by the demux loop.
- Durability: explicit flush requests and engine closes now sync_all;
flush_and_join's ack means fsync-durable (survives power loss), while
the consumer's idle-cadence flushes stay cheap (OS buffer only).
New gate (prof_gate 5/5): engine_teardown_closes_profile - drops an
engine, proves its file stops growing across two heartbeat intervals
after the close (FIFO control channel: the flush ack implies the close
already ran), and proves later engines still profile normally.
Verified before commit: bex_events 53/53, prof_gate 5/5, miri 32/32,
clippy clean. (Ring sync code untouched - the loom suite from the
previous commit still covers it.) Design doc 0.1 updated.
Reconciles paulo/bex-event-identity-metadata (M0 identity, program metadata, the M1 $id surface) with the M2-M4 ring/consumer/producer implementation. One id universe, one transport. Taken wholesale from #3730: - ids.rs (ProcessEuid/EngineId/ProgramId/BexThreadId/FunctionId/CallId, ThreadRef/CallRef wire encoding, RuntimeId) - the M0 contract. - metadata.rs (rich FunctionMetadataTable + ProgramMetadata). - The $id language surface (ns_id/id.baml, compiler lowering) and identity on host RuntimeEvents (CallRef on the bridge proto). - EventSink::send_event_file_header (kept; fed at engine construction) and send_disk_event (kept as opt-in sink surface). Unified (the divergence points): - Call ids: ONE universe, minted VM-side per call (plan invariant 5). The span machinery's engine-side counter (SpanState.next_call_id) is gone; traced spans read the VM's current call id at FunctionEnter, so host-event identity, $id strings, and .bamlprof records carry byte-identical ids. - $id is now VM-live (bex_ref_seed + current_call_id + per-call override) instead of engine-set per exec step from the traced-span stack: it works in every function, traced or not, profiling on or off, with CallRef encoding paid only when $id is actually read. baml.id.set() records SetFunctionId into the ring (stream clock). - Function ids: 1-based sequential in pool order (0 = unassigned), stamped on Function pre-heap AND used by build_program_metadata - the metadata table replaces the interim plan-2.6 provider and now feeds the .bamlprof header (program_id = hex ProgramId). - One engine-id mint (EngineId) and one logical-thread-id mint shared by profiling records and host-event identity. - ThreadOutcome::SettledChild keeps the engine-internal ChildSettleKind; Paulo's span-close emissions for erroring/cancelled children are retained on top. Dropped (interim scaffolding from #3730, superseded by the ring): - The per-call RuntimeCallNotify VM yield + runtime_call_frames + pending_disk_events + the engine's shadow span stack for non-traced calls (a yield per call breaks the exec loop ~1M times/s on call-heavy code; the ring push is ~63ns inline). - The DiskEventV1-via-EventSink per-call emission paths (StartThread / CallFunction / EndFunction / EndThread / SetId through the sink); the disk stream is the ring -> bex-prof-consumer -> .bamlprof. - Per-step CurrentBexIdentity maintenance (incl. its per-step CallRef base64 encode). Tests: tracing.rs identity tests pass against the unified VM ids unchanged (thread 1 / call 1 / CallRef round-trip); its three sink-stream lifecycle tests are superseded by prof_gate.rs and removed; the $id tests keep their behavioral asserts and the SetFunctionId record assertion moved to prof_gate::set_function_id_recorded (which caught a real bug: the VM record buffer was sized for CallFunction and panicked on the 41-byte SetFunctionId - fixed). New gate total 6/6; engine suite 31 binaries green; bex_events 61; workspace clippy clean. Boxed bex_events_native's BufferedEvent large variants (clippy).
The ring/producer/consumer contract this branch implements: the locked concurrency decision ledger (D1-D7), the codebase map, the ring specification with its memory-ordering table, the record/artifact shapes, the phased PR gates, and the review invariants - plus the coordination seams with the M0/M1 identity work (now merged here, see PR #3733). Added verbatim from the planning doc with a status banner recording implementation state and the documented deviations.
Internal planning docs live in boundaryml/thoughts (antonio/ bex-event-stream-impl-plan.md, with the implementation-status banner); the in-repo copy was a sharing convenience that doesn't belong here.
…napshots Three CI failures on PR #3733, all inherited or latent: - prek's wasm clippy (-D warnings): the prof consumer half is compiled but unreachable on wasm32 (profiling forced off; the deferred cooperative drain would be its caller), so its API is dead code there. The blanket allows removed in the review-2 cleanup were validated native-only; restored as targeted cfg_attr(target_arch = "wasm32", allow(dead_code)) on the four prof files. - test_vm_param_matches_vm_usage (all OSes): latent test weakness exposed by the first zero-param vm-less builtin (baml.id.new). The contains-pattern disambiguated same-name methods by params only, so zero-param methods aliased across traits (spawn.CancelToken.new(vm: &mut BexVm, ) matched the pattern built for baml.id.new()). Patterns now include the return type. - snapshot tests: accepted the 8 snapshots changed by the new baml.id namespace (HIR/TIR/MIR/codegen/describe listings plus the global-index renumbering in the bytecode-format listings). Verified the diffs are only the id namespace + index shifts. Verified locally with CI's own lines: the wasm clippy task config, cargo insta test (no snapshots to review, no unreferenced), and cargo nextest run --all-features -E 'not package(baml_tests) and not package(/^sdk_test_/)' - 3433/3433 passed.
Fixes every pre-merge item from TASK/REVIEW.md §10:
- §2.1: unwind resync — the VM yields Unwound{frames_remaining} whenever an
exception unwind pops notified frames; the engine closes spans by frame
depth, self-heals on enter/exit depth mismatches, and the Complete arms
(root and child) close stray spans loudly instead of blind-popping
- §2.2: $id overrides persist on EngineSpan (written back when the SetId
event is drained), so they survive nested calls and spawns
- §2.3: interrupt() consumes notification states while the interrupt frame
is alive; a filter exception escaping the boundary fails loudly
- §2.4/§4.3: CallFunction is never skipped (reserved unknown-function
sentinel row); call notifications carry the resolved function pointer and
the engine resolves identity via a prebuilt addr->FunctionId map — the
per-call name clones, O(F) table scans, and display-name misattribution
are gone; call_callable gets real callee identity
- §2.5: timestamp_ns is monotonic-since-process-start (bex_events::clock);
the header's started_at_epoch_ns is the matching wall anchor so
wall = started_at + ts composes
- §2.6/§2.7: root cancellations read Cancelled/Cancelled; baml.sys.exit(0)
maps to Ok/Completed (non-zero to Error); abnormal spawn-task arms drain
open spans before EndThread
- §3.x: compiler $id holes — compound assignment, non-string assignment,
member access (incl. ?.), call-site labels, and $id bindings (let, param,
destructure shorthand, function names) are targeted compile errors; the
dead AST rewrite is removed; MIR's silent-temp lvalue fallback fails
loudly; baml.id.set declares throws InvalidArgument and the $id = e sugar
is held to the same throws contract
- §5.x: per-line engine_id on the interim JSONL (EventSink::send_disk_event
carries EngineId), bounded-retry header sends, atomic per-line writes,
documented lossiness
Contract test suite per §9: assert_balanced/assert_threads_closed helpers
used by every disk-event test (and retrofitted into the pre-existing loose
ones), T1-T26, T28, T31-T34, two-engine scoping, ids.rs decode edges, and an
end-to-end JSONL consumer test. Entropy pass per §7: emit_span_end
consolidation, single spawn EndThread exit point, BexCallId rename, doc
comments on the contract types, TODO(bep-053) markers on the heuristics.
Also fixes a pre-existing clippy 1.96 lint in baml_release (the pre-commit
hook denies warnings workspace-wide, so it gated this commit).
TASK/REVIEW-RESPONSE.md maps every finding to its fix and test (stable
T-numbers), records the design decisions, and lists the items deliberately
left for the cross-team sync (call-site $id form, yield gating /
VM-owns-call_id seam, lazy CallRef minting, status naming, call-heavy bench,
serde-derive migration).
Co-Authored-By: Claude Fable 5 <[email protected]>
Merges the profiling-ring stack (PR #3733) with the BEX identity review-fix suite (3c1929d). Antonio's VM-owned identity model wins the contested seams — it is exactly the §4.1/§4.2 refactor the review response deferred to him: call ids minted by the VM, lazy CallRef encoding on $id read, overrides keyed by call_id, per-call lifecycle in the lock-free ring instead of per-call engine yields. Resolution decisions: - Engine-pushed identity (CurrentBexIdentity, pending_disk_events, the SetId drain, EngineSpan.id_override) is deleted; $id reads/overrides are VM-sourced. - The interim JSONL disk-event stream is gone (its replacement — the ring + .bamlprof — landed). Its behavioral pins are ported to prof_gate.rs at the artifact level: caught-exception balance, call_callable identity, cancellation/exit/child-thread statuses, sentinel header rows, same-display-name attribution. - RuntimeCallNotification is deleted with the per-call yield; the Unwound notification (review §2.1) is ported onto SpanNotification so the traced span stream keeps timely, self-healing closure of frames popped by caught exceptions, and so a watch-filter exception escaping the interrupt boundary still fails loudly (the unwinder counts the boundary crossing as a forced-yield trigger). - Function ids: Antonio's 1-based sequential scheme is kept; the ptr->id map is built from the same stamping walk (the merge had left it reading sequential ids as pool indices), and the unknown-function sentinel row is re-seated at max+2. Per-call resolution stays pointer-based (review §4.3); the display-name scan is gone. - run_thread_event_loop's ring EndThread mapping now distinguishes baml.sys.exit(0) (Completed) from exit(n != 0) (Errored), per the review's pinned §2.6 policy; the spawn task's abnormal arms drain open spans before the thread closes (§2.7). - ids.rs keeps the review rename (CallId -> BexCallId); Antonio-side references updated. - VM-level notification contracts (bex_vm/tests/call_notifications.rs) now pin the traced stream; engine tracing tests keep every $id and span assertion and drop only the dead JSONL assertions. Known gaps recorded for the cross-team sync (pinned as KNOWN GAP comments in prof_gate.rs): cancellation does not close the open frame (no EndFunction; ring proto has no Cancelled function status), and exit(0) frames end Error at function level (thread level reads Completed) because exit is a synthetic VM throw. Co-Authored-By: Claude Fable 5 <[email protected]>
A 30-agent verification pass over merge 128bb22 (5 dimensions, every finding independently re-verified against the code) confirmed one behavioral regression, two latent defects, and a set of stale comments/docs from the resolution. Fixed here: - $id override nesting (review §2.2, T4-T7): the merge had narrowed the override to a single VM slot, so a callee that also called `baml.id.set` silently destroyed its caller's override ($id reverted to the default CallRef after the callee returned; empirically reproduced). Replaced with a per-call override stack on the VM: set replaces-or-pushes the current call's entry, reads match only the top against current_call_id, and prof_exit_call pops the exiting call's entry (>= guard self-heals against silent pops). Pinned by the new tracing test id_override_survives_callee_override. - Doubled error-drain emit in run_thread_event_loop_inner's escaped-throw arm (introduced by the ring branch's ChildSettleKind restructuring; currently a no-op only because the first drain empties the span stack). Single emission point restored. - A watch filter yielding a sys-op stranded the armed sysop CallFunction (its only close site, the engine's SysOp arm, is unreachable from interrupt()): the mini-runner now closes the pending pair with EndFunction{Error} before failing loudly. Comment/doc truthfulness (no behavior change): unknown-function sentinel is header-only today (unresolvable callees stamp function_id 0 — re-pointing them at the sentinel row is an open cross-team item); interrupt-internal calls DO mint call ids and ring records (only the span stream is suppressed); EventSink::send_disk_event / disk_event_to_jsonl / NativeEventSink disk paths marked producer-less legacy; RuntimeId contract now names the live SetFunctionId record; removed the orphaned pool-size comment, a dangling RuntimeCallNotify cross-reference, a stale only_used_in_recursion allow, and wrong test names in tracing.rs port notes; prof/mod.rs naming note updated for the landed ids.rs. Recorded for the cross-team sync (not changed here): root-thread streams emit the entry CallFunction before StartThread (children are the opposite — pre-existing ring-branch ordering), and a spawned task dropped at an await before its event loop runs leaks an unclosed StartThread/CallFunction (abnormal host teardown only). Co-Authored-By: Claude Fable 5 <[email protected]>
TASK/RECONCILIATION.md: what each parent branch did, where they diverged, every reconciliation decision, the post-merge review findings, and the full pending-items list for landing into canary. Co-Authored-By: Claude Fable 5 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds BEX runtime identity and profiling ring support across compiler contracts, VM/engine execution, on-disk ChangesBEX identity and profiling pipeline
Sequence Diagram(s)sequenceDiagram
participant BexEngine
participant RingRegistry
participant BexVm
participant ProfConsumer
participant ProfileWriter
BexEngine->>RingRegistry: register_engine_metadata
BexEngine->>BexVm: attach prof_ring and call ids
BexVm->>RingRegistry: ring_for_engine
BexVm->>ProfConsumer: prof records via ring
ProfConsumer->>ProfileWriter: append .bamlprof events
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
# Conflicts: # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap
…my, cancel drain, StartThread-first
Decisions 1-9 (RECONCILIATION.md §7, resolved by Antonio 2026-06-11):
- FunctionEndStatus is now the closed frame-fate taxonomy {OK, ERRORED,
CANCELLED, EXITED}: ERROR renamed ERRORED (tag unchanged), CANCELLED=2
and EXITED=3 added. Statuses describe the frame's fate, not the
program's outcome.
- The unwinder peeks the thrown value's panic class once per unwind
(prof_unwind_status): Exit→Exited, Cancelled→Cancelled, else Errored.
baml.sys.exit's own native pair closes Exited
(prof_native_error_status); sysop pairs classify op errors the same way
(prof_sysop_error_status — a host-abandoned CompletionHandle is
cancel-classed without the thread's token firing).
- Terminated threads never strand open calls: prof_drain_open_calls
closes every open frame innermost-first at the seven blocks that end a
thread without unwinding the VM — six cancel blocks (Cancelled) plus
the unobserved fire-and-forget child-error surfacing (Errored; found by
the post-decision adversarial review). The weakened
assert_balance_allowing_unended helper is deleted; cancel/exit tests
assert full balance.
- Root StartThread moved ahead of set_entry_point: every thread's first
record is its StartThread (wire invariant, pinned in
reconstruction_smoke). The EndThread pairing now relies on the
straight-line stretch into run_thread_event_loop — comments pin both
ends.
- Doc-only rulings recorded in the proto header: function_id 0 blessed as
the unattributable-call wire contract; watch-filter calls stay in the
stream (code that runs); SetFunctionId is per-set, last-wins;
foo($id = ...) deferred. EXITED's thread-level exit-code mapping is
scoped to root threads (children settle Errored like any unhandled
throw).
Gates: prof_gate 15/15 (new: unobserved_child_error_drains_parent_frames),
tracing 23, call_notifications 7, bex_events 68, loom 28/28, miri 32/32,
nextest 3468/3468, clippy clean.
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
|
No description provided. |
…-reconcile # Conflicts: # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap
…-reconcile # Conflicts: # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded.snap # baml_language/crates/baml_tests/tests/bytecode_format/snapshots/bytecode_format__bytecode_display_expanded_unoptimized.snap
…onsumer (BoundaryML#3759) ## Problem `minstant-0.1.7` registers a `#[ctor]` static initializer that busy-spins **≥20 ms calibrating the TSC before `main()` in every process that links `bex_events`** — packed binaries, `baml-cli`, the LSP, and every nextest test process — even with `BAML_PROFILE=0`. Its calibration loop (`_cycles_per_sec`) requires ≥10 ms per sample window and ≥2 converging windows by construction. Measured impact: an empty packed program (`function main() -> int { return 0; }`) ran **10.8 ms pre-tracing → 31.3 ms** on canary. This constant per-process tax was the dominant end-to-end regression from the tracing merge (BoundaryML#3740) — it inflated every short-running benchmark by +50–145% and is a large chunk of the test-suite wall-time increase. Secondary problems: minstant only uses the TSC on Linux x86 (Macs silently fall back to the OS clock), and the producer hot path paid a tick→ns conversion on every clock read. ## Fix - **Producers stamp raw ticks** (`clock::now_ticks()`): `rdtsc` on x86_64 (gated on a CPUID invariant-TSC probe), `mrs CNTVCT_EL0` on aarch64, monotonic-`Instant` fallback otherwise. No conversion, no calibration, no ctor on any producer path. The anchor is forced inside `now_ticks()`, so every stamped tick postdates the process base regardless of init order. - **The consumer owns tick→ns conversion** (`TickConverter`, built on the consumer thread): exact rational rates where the platform states them (`mach_timebase_info` on macOS — Apple Silicon's 125/3 handled exactly; `CNTFRQ_EL0` on Linux ARM); for the x86 TSC, a ~2 ms two-point estimate at consumer start, refined once across a ≥1 s window at heartbeat cadence with piecewise-linear anchor continuity (per-thread `timestamp_ns` stays monotone across the rate swap; late-draining ring events convert through the previous segment). - **Disk format unchanged**: `.bamlprof` timestamps remain nanoseconds; `wall(event) = started_at_epoch_ns + timestamp_ns` holds; `prof_gate` needed zero source changes. The header gains informational `clock_kind` / tick-rate rational / `clock_quality` fields (proto3-additive). - `minstant` (and the `[email protected]` it pulled in) removed from the workspace. ## Measured (Linux x86, 32-core dev box) | Metric | before | after | |---|---|---| | Empty packed binary startup (profiling on) | 31.3 ms | **15.2 ms** | | Same, `BAML_PROFILE=0`, vs pre-tracing baseline | +20.5 ms | **+1.1 ms** | | `now_ticks` read (prof_clock bench) | 8.45 ns (minstant) | **8.33 ns** | | Tracing on-vs-off, divan pure-call-1m | ~42 ns/call-pair | **~24 ns/call-pair** | The per-pair improvement comes from the two tick→ns conversions leaving the VM hot path. End-to-end (speedtest corpus, packed binaries): small/loop-heavy workloads return to ≈pre-tracing (+3–11%); remaining overhead concentrates proportionally to traced call volume. ## Verification - `cargo test -p bex_events` (51), `cargo test -p bex_engine --test prof_gate` (17/17) - Full CI mirror: `rustup run 1.93.0 cargo test -p bex_events -p bex_vm -p bex_engine --all-features` — all green - loom (`--cfg baml_loom`, 34) and miri (33) prof suites - clippy `--all-targets` clean; wasm32 `cargo check` clean - `.init_array` back to 53 entries (the ctor is gone); `cargo tree -i minstant` / `-i [email protected]` → no matches - New unit tests: cross-thread tick ordering, converter rate sanity (catches a swapped mach timebase rational), refinement monotonicity across the rate swap, pre-base clamp, identity passthrough ## Notes for reviewers - ARM paths are validated by compilation + unit tests that will exercise them on the macOS-ARM CI leg (`converter_rate_sane` fails loudly on a wrong rational). Known monitor-don't-block risk: 24 MHz CNTVCT granularity on M1–M3 slightly raises timestamp-tie probability in prof_gate's large streams; ties are stable-sort-safe. - The consumer blocks ~2 ms once at startup for the coarse TSC estimate (x86 only, profiling on only); events transcoded in the first ~1 s carry ~0.1% rate error, recorded as `CLOCK_QUALITY_COARSE` in the header. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Profiling now stamps raw hardware ticks and converts them to nanoseconds on the consumer side for improved consistency and monotonicity. * Trace file headers record clock diagnostics (clock kind, tick→ns rate, and quality) so exported traces include clock metadata. * **Chores** * Removed an external timing dependency; benchmarks and tests updated to measure ticks and conversion separately. * Profiling is stubbed/disabled on unsupported targets (e.g., wasm/miri). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
BEX Identity × Profiling Ring — Reconciliation Handoff
Branch:
paulo/bex-prof-ring-reconcile→ targetscanary.What it contains: everything from both BEX tracing workstreams, reconciled:
Paulo's event identity + metadata + review-fix suite (
paulo/bex-event-identity-metadata,PR #3730 + commit
3c1929dfd) merged with Antonio's profiling ring stack(
antonio/bex-prof-ring, PR #3733, tip27ec7c0b4), plus a post-mergeadversarial review pass. Landing this branch lands both workstreams.
Commit map:
d318a6ceb$id, metadata table, interim JSONL (PR #3730 — already inside Antonio's branch via his mergea7f247f79)3c1929dfdTASK/REVIEW.md/TASK/REVIEW-RESPONSE.md)bb67b34a7..27ec7c0b4.bamlprof, teardown, CI128bb22bd80a2c5da6$idregression + latent defects + doc truthfulness)1. What Paulo's branch did (M0/M1 + review hardening)
Design doc:
TASK/TICKET.md. Post-impl notes:TASK/POST-IMPLEMENTATION-NOTES.md.ids.rs(M0) — the identity quadProcessEuid / EngineId / BexThreadId / BexCallIdplus
FunctionId,CallRef/ThreadRef/RuntimeIdwith reversible stringencodings (
decode(encode(x)) == x), versioned base64url, hardened decodeedges (malformed/truncated/cross-type all fail cleanly, unit-tested).
$id(M1) — language primitive: read returns the current call'sidentity;
baml.id.new()mints an override UUID;baml.id.set(...)/$id = ...installs it. Compiler-side misuse diagnostics(
RuntimeIdCompoundAssignment,RuntimeIdMemberAccess,RuntimeIdCallSiteArgument, reserved-name checks, throws-factregistration for the implicit
id.set), all snapshot-pinned.FunctionMetadatarows (fqn,display name, source span, kind, origin, owner type, lambda metadata,
BEP-053 join fields), synthetic spawn-closure and unknown-function rows,
carried in the artifact header.
DiskEventV1records throughEventSink(a stopgap until the ring landed; deleted in this merge).3c1929dfd) — 30+ fixes pinned by tests T1–T34:caught-exception span desync (
Unwoundnotification + engine depthself-healing),
$idoverride persistence across nested calls, watch-filtercrash fix + scoping, never-skip CallFunction + sentinel attribution,
monotonic process-anchored timestamps, cancel/exit status mapping,
multi-engine scoping, header delivery robustness, wire-shape pins.
2. What Antonio's branch did (M2–M4 ring stack)
Design docs (private
BoundaryML/thoughtsrepo):antonio/bex-event-stream-design-v2.md(the spec) and
antonio/bex-event-stream-impl-plan.md(the locked decisionledger D1–D7 + codebase map). Implemented in full as PR1–PR5:
(engine, os-thread): lock-free push (memcpy +one Release store), lossless-by-growth, free-list recycling, append-only
registry, orphan→pool→claim lifecycle. loom + miri gated
(
bex_events/src/prof/concurrency_tests.rs, cfgbaml_loom).call_idper call unconditionally(frames carry it), writes
CallFunction/EndFunctionraw records atcall/return/unwind; engine emits
StartThread/EndThread(+ sysop pairs)on cold paths; D5a ring-pointer refresh once per exec resume.
std::thread, drains rings,transcodes raw POD → protobuf
DiskEventV1, writes per-engine.bamlprof(prof/proto/bamlprof.proto), heartbeats, durable flush ack,torn-tail-tolerant reader, engine teardown closes the file.
a7f247f79pulled PR Implement BEX event identity and program metadata #3730 into "one id universe": herewrote
$idto be VM-sourced and lazy — which is exactly the §4.1(VM-owns-call_id, no per-call engine yield) and §4.2 (lazy CallRef
minting) items Paulo's review response had deferred to him.
the pure-call microbench, ~0–4.4% realistic. Profiling ships
default-off (
BAML_PROFILE=1opt-in) per PR5's rollout note.3. Where the branches diverged
Antonio's merge predated Paulo's review fixes, so
3c1929dfd(writtenagainst the engine-owned model) collided with his 12 ring commits on the
same seams — 54 conflict hunks across 7 files, plus semantic divergences no
textual conflict surfaced:
3c1929fd)27ec7c0b4)CurrentBexIdentityinto VM each step; engine mints call idscall_id_counter,current_call_id,bex_ref_seed; lazy CallRef encode on read$idoverrideEngineSpan.id_override, written back at the SetId draincurrent_id_override, keyed by call idRuntimeCallNotificationyield per call → engine emitsEventSink::send_disk_event(engine_id per line, header retry, T31–T34).bamlprof; JSONL deletedUnwound{frames_remaining}; engine closes spans by depthEndFunction{Error}per unwound frame into the ringFunctionobjects; ids snapshot-pinnedCancelled, exit(0)→Ok/Completed, exit(n)→Error (T16–T20)ThreadEndStatus{Completed,Cancelled,Errored}; all exits → Completed; no function-level CancelledBexCallId(avoidssys_types::CallIdclash)CallId, aliased in engine4. What the reconciliation decided (merge
128bb22bd)Principle: Antonio's architecture wins the seams he owns (it implements
the agreed deferral); Paulo's behavioral guarantees are ported onto it as
code where still applicable and as artifact-level tests everywhere.
CurrentBexIdentity,pending_disk_events,the SetId drain,
EngineSpan.id_override— deleted.$idreads VM statelazily;
bex_enginere-exports ourBexCallIdnaming and allAntonio-side
ids::CallIdreferences were renamed.bex_engine/tests/prof_gate.rs:caught_exception_keeps_ring_balance,call_callable_has_real_identity_and_balance,root_cancellation_ends_thread_cancelled,spawned_child_cancellation_ends_child_cancelled,spawned_child_error_ends_child_errored,sys_exit_status_mapping,sentinel_rows_present_in_header,same_display_name_functions_are_not_misattributed.bex_engine/tests/jsonl_artifact.rsdeleted (T31 coverage isprof_gate's header/balance checks).
Unwoundported ontoSpanNotification.RuntimeCallNotificationdied with the per-call yield, but the traced (
@trace) span stream stillneeds the §2.1 fix: the unwinder counts popped traced frames (plus one
if it crosses the watch-filter interrupt boundary — that forced yield is
what keeps an escaping filter exception loud instead of letting the
program's completion be consumed as the filter verdict) and the engine
closes spans by depth.
bex_vm/tests/call_notifications.rspins this onthe SpanNotification stream.
had the engine reading sequential ids as pool indices (would have
misattributed every function in the ptr→id map) and the unknown sentinel
at a pool-derived id. Fixed: the ptr→id map is built from the same
stamping walk; sentinel rows sit at max+1 (spawn-closure) and max+2
(unknown); per-call resolution stays pointer-based (no name scans on any
per-call path).
run_thread_event_loop's EndThread mapping now distinguishesbaml.sys.exit(0)(Completed) fromexit(n≠0)(Errored) per T19'spinned policy; root/child cancellation reads
Cancelled. Thefunction-level
Cancelledstatus does not exist in the proto — seepending items.
open spans before the wrapper's EndThread.
tracing.rskeeps every$id/RuntimeEventassertion (22 tests), dropping only dead JSONL assertions, each with a
pointer comment to its prof_gate replacement.
5. Post-merge adversarial review (commit
80a2c5da6)A 30-agent verification pass (5 dimensions — lost fixes, merge junctions,
$idsemantics, ring balance, doc consistency — every finding independentlyre-verified) confirmed and fixed:
$idoverride nesting regression (the one real bug). The mergedsingle-slot
current_id_overridemeant a callee callingbaml.id.setdestroyed its caller's override (empirically reproduced: caller's
$idreverted to the default CallRef after the callee returned). Replaced with
a per-call override stack on the VM (
id_overrides), popped with theexiting frame in
prof_exit_call(>=guard self-heals). Pinned byid_override_survives_callee_override.run_thread_event_loop_inner(introduced by the ring branch'sChildSettleKindrestructuring; currently a no-op only because the firstdrain empties the stack). Single emission restored.
(its only close site — the engine's SysOp arm — is unreachable from
interrupt()). The mini-runner now closes the pending pair withEndFunction{Error}before failing loudly.architecture (sentinel attribution, producer-less JSONL APIs, dangling
cross-references,
SetIdvsSetFunctionId, stale M0 notes).Refuted (checked, not real): prof-clock suspend-flake claim, dropped-header
claim, sysop early-return-leak claim.
6. Verification status
cargo testgreen acrossbex_vm,bex_events,bex_events_native,bex_engine(incl. prof_gate 14, tracing 23, call_notifications 7),bridge_ctypes,baml_cli(210),baml_tests(1,603 snapshots), and therest of the workspace — 439/439 in the affected crates at tip.
cargo clippy --all-targets0 warnings;cargo fmt --checkclean.baml_clidescribe listing (line numbersshifted by
ns_id/id.bamldoc/throws changes — content-identical).bridge_python(pyo3 build),sdk_test_typescript_node(tsc/vitest),loom/miri jobs (
--cfg baml_loom), wasm32 builds.7. Pending items / things to check before & after landing
Decisions needed (Antonio + Paulo)
Cancelledstatus. Ring protoFunctionEndStatusis{Ok, Error}— so cancellation cannot mark frames, and the old JSONLCancelledmapping has no ring equivalent. Decide: addFUNCTION_END_STATUS_CANCELLED(additive, non-breaking) or documentError-on-cancel.
// KNOWN GAPinprof_gate.rs::root_cancellation_ends_thread_cancelled): engine-sidecancellation doesn't unwind the VM, so the frame open at cancel time gets
no
EndFunctionat all (root and spawned children alike; thein-flight sysop pair does close). The thread closes
Cancelled. The oldJSONL stream drained every open span. Decide: engine-side drain of open
call ids at cancel, or pin "cancelled threads may strand open calls" as
renderer contract.
prof_gate.rs::sys_exit_status_mapping): exit is a synthetic VM throw,so
mainendsEndFunction{Error}even forexit(0)(thread level iscorrect: Completed). Old contract said Ok. Needs either a
VM-visible exit flag or an accepted-deviation note.
ErroredvsErrornaming (REVIEW §6) — proto saysERROREDforthreads,
ERRORfor functions; fine, but confirm and close the item.function_id: 0("unassigned"); the header'sbaml.<unknown-function>row (max+2) is reserved but never referenced by records. Decide:
re-point the id-0 paths (
vm.rsprof_enter_call(0)for the$entry::wrapper, host-closure sysop pairs, interrupt callees) at the sentinel, or
bless id-0 as the wire contract and keep the row as a display bucket.
and emit balanced ring records attached to the interrupted call (old
scoping decision said: no identity, no events). Balanced, but pollutes
timelines. Decide whether to gate ring emission under
interrupt_frame.is_some().threads emit the entry
CallFunctionbefore theirStartThread;children are the opposite. Renderer must not assume StartThread-first —
either document, or move the root StartThread emission ahead of
set_entry_point(uniform invariant; see review notes for thebalance-safety argument).
$idcall-site formfoo($id = ...)(§3.6): both design docs showit; implementation is callee-side assignment with a targeted diagnostic.
Product decision — changes
SetFunctionIdadjacency.set()call (review wantedonce-per-override). Probably fine — consumer takes last-wins — but
confirm renderer semantics.
Follow-up work (either of us, post-land)
EventSink::send_disk_event/send_event_file_header(+ FanOut/nativeimpls and
BufferedEvent::Disk/Header),serialize::disk_event_to_jsonlbex_events::clock,bex_events::DiskEventV1(legacy Rust enum, distinct from the proto), and
BexEngine::event_file_header_v1if no host needs it. All are dead buttested; docs now say so explicitly.
dropped at an await before its event loop runs leaks an unclosed
StartThread/CallFunction. A small RAII closer in
spawn_thread_inner'stask body would close it; sketch is in the review notes.
ids.rsnewtypes inRawRecord(fields are plain u64 —the remaining M0 unification step).
and dropped-future truncation policy (T22), against
.bamlprof.tools/speedtest/workloads/(Antonio'smicrobench exists; the speedtest-integrated workload doesn't).
survives item 10; otherwise dies with it.
BAML_PROFILEonce PR4/PR5 gates have CIhistory (his PR5 note), in its own one-line PR.
Things to double-check in review
bex_engine/src/lib.rs/bex_vm/src/vm.rs(the merge commit message lists every decision; the adversarial pass
specifically hunted junction errors and found two, both fixed).
vm.rstry_unwind_exception: the popped-traced-frame count + theinterrupt-boundary crossing both force the
SpanNotification::Unwoundyield — confirm the crossing bump matches your mental model of the filter
contract.
prof_exit_callruns unconditionally(identity semantics, not profiling) — placed before the ring gate.
build_program_metadata+ the stamping walk +function_ids_by_ptrmuststay the same walk (comments now say so) — any future reorder breaks
table↔record id agreement silently.
not.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
baml.idnamespace:current()returns the active invocation ID,new()creates overrides, andset()installs them.bamlproffile generation for observabilityDocumentation