Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
8 changes: 7 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. */
executionTraceId: bigint;
};

// ── Cache ───────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -338,5 +340,9 @@ 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 ?? [],
executionTraceId: 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
Loading