Skip to content
Merged
6 changes: 6 additions & 0 deletions .changeset/playground-dead-code-trace-manifest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@stackables/bridge": patch
"@stackables/bridge-core": patch
---

Bridge Trace IDs - The engine now returns a compact Trace ID alongside your data (e.g., 0x2a). This ID can be decoded into an exact execution map showing precisely which wires, fallbacks, and conditions activated. Because every bridge has a finite number of execution paths, these IDs are perfect for zero-PII monitoring and bucketing telemetry data.
4 changes: 3 additions & 1 deletion packages/bridge-compiler/src/execute-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export type ExecuteBridgeOptions = {
export type ExecuteBridgeResult<T = unknown> = {
data: T;
traces: ToolTrace[];
/** Compact bitmask encoding which traversal paths were taken during execution. */
executionTrace: bigint;
};

// ── Cache ───────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -338,5 +340,5 @@ export async function executeBridge<T = unknown>(
} catch (err) {
throw attachBridgeErrorDocumentContext(err, document);
}
return { data: data as T, traces: tracer?.traces ?? [] };
return { data: data as T, traces: tracer?.traces ?? [], executionTrace: 0n };
}
32 changes: 32 additions & 0 deletions packages/bridge-core/src/ExecutionTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {
matchesRequestedFields,
} from "./requested-fields.ts";
import { raceTimeout } from "./utils.ts";
import type { TraceWireBits } from "./enumerate-traversals.ts";
import { buildTraceBitsMap, enumerateTraversalIds } from "./enumerate-traversals.ts";

function stableMemoizeKey(value: unknown): string {
if (value === undefined) {
Expand Down Expand Up @@ -145,6 +147,17 @@ export class ExecutionTree implements TreeContext {
private forcedExecution?: Promise<void>;
/** Shared trace collector — present only when tracing is enabled. */
tracer?: TraceCollector;
/**
* Per-wire bit positions for execution trace recording.
* Built once from the bridge manifest. Shared across shadow trees.
*/
traceBits?: Map<Wire, TraceWireBits>;
/**
* Shared mutable trace bitmask — `[mask]`. Boxed in a single-element
* array so shadow trees can share the same mutable reference.
* Uses `bigint` to support manifests with more than 31 entries.
*/
traceMask?: [bigint];
/** Structured logger passed from BridgeOptions. Defaults to no-ops. */
logger?: Logger;
/** External abort signal — cancels execution when triggered. */
Expand Down Expand Up @@ -726,6 +739,8 @@ export class ExecutionTree implements TreeContext {
child.toolFns = this.toolFns;
child.elementTrunkKey = this.elementTrunkKey;
child.tracer = this.tracer;
child.traceBits = this.traceBits;
child.traceMask = this.traceMask;
child.logger = this.logger;
child.signal = this.signal;
child.source = this.source;
Expand Down Expand Up @@ -761,6 +776,23 @@ export class ExecutionTree implements TreeContext {
return this.tracer?.traces ?? [];
}

/** Returns the execution trace bitmask (0n when tracing is disabled). */
getExecutionTrace(): bigint {
return this.traceMask?.[0] ?? 0n;
}

/**
* Enable execution trace recording.
* Builds the wire-to-bit map from the bridge manifest and initialises
* the shared mutable bitmask. Safe to call before `run()`.
*/
enableExecutionTrace(): void {
if (!this.bridge) return;
const manifest = enumerateTraversalIds(this.bridge);
this.traceBits = buildTraceBitsMap(this.bridge, manifest);
this.traceMask = [0n];
}

/**
* Traverse `ref.path` on an already-resolved value, respecting null guards.
* Extracted from `pullSingle` so the sync and async paths can share logic.
Expand Down
Loading