Skip to content

Commit dc670b2

Browse files
Copilotaarne
andauthored
feat: runtime execution trace bitmask tracking with manifest view (#119)
* Initial plan * feat: add execution trace bitmask to core engine - Add bitIndex to TraversalEntry for bitmask encoding - Add buildTraversalManifest (alias for enumerateTraversalIds) - Add decodeExecutionTrace to decode bitmask back to entries - Add buildTraceBitsMap for runtime wire→bit lookup - Add traceBits/traceMask to TreeContext interface - Inject trace recording in resolveWires (primary/fallback/catch/then/else) - Add executionTrace to ExecuteBridgeResult - Propagate trace mask through shadow trees - All existing tests pass (133 execute-bridge + 64 resilience + 14 traversal) Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * test: add comprehensive tests for execution trace feature - Test buildTraversalManifest alias and bitIndex assignment - Test decodeExecutionTrace with empty/single/multiple/round-trip - Test end-to-end trace collection: primary, fallback, catch, then/else, const - All 27 tests pass Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * feat: add execution trace and manifest view to playground - Add executionTrace to RunResult type in engine - Pipe executionTrace through runBridgeStandalone - Add getTraversalManifest and decodeExecutionTrace helpers to engine - Show execution trace badge (hex) in ResultView - Add Manifest tab to Bridge DSL panel header - ManifestView shows all traversal entries with kind badges - Active paths highlighted with ring + dot based on execution trace - Works across mobile and desktop layouts Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * refactor: address code review feedback - Rename dslTab → activeDslTab for clarity - Rename fi → fallbackIndex in applyFallbackGates loop - Add comment explaining bitmask check in decodeExecutionTrace - Add INVARIANT comment for traceMask/traceBits coupling - Document bitIndex assignment lifecycle in enumerateTraversalIds Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * feat: BigInt trace mask, manifest grouping/filtering, scroll fix - Switch traceMask from number to bigint for unlimited entry support - Update all recording helpers to use BigInt bitwise ops - Update decodeExecutionTrace, ExecuteBridgeResult, TreeContext - Update playground engine, ResultView, Playground types - ManifestView: group entries by wire, show group headers for alternatives - ManifestView: add "Show alternatives only" toggle filter - ManifestView: fix scrolling with h-full / max-h-[60vh] - Update tests to use bigint assertions - All 1143 tests pass Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * fix: add aria-label to manifest group count for accessibility Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * feat: source descriptions, alias labels, empty-array grouping, always-on toggle - Add `description` field to TraversalEntry with human-readable source info (e.g., "api.username", "|| \"Anonymous\"", "catch continue", "= \"SBB\"") - Fix alias wires showing as `*` — use `to.field` for `__local` module targets - Fix empty-array entries grouped under same `*` — assign unique wireIndex per scope - Handle map resolves tool, input, context, pipe, and alias refs to handle aliases - Filter toggle always visible regardless of whether alternatives exist - Consistent group headers shown for all entries (not just alternatives) - Empty-array entries show iterator variable in description (e.g., "c[] empty") - All 1143 tests pass Co-authored-by: aarne <82001+aarne@users.noreply.github.com> * UI tweaks * LSP for dead code in playground * Docs --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aarne <82001+aarne@users.noreply.github.com> Co-authored-by: Aarne Laur <aarne.laur@gmail.com>
1 parent f1aefca commit dc670b2

19 files changed

Lines changed: 1413 additions & 116 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@stackables/bridge": patch
3+
"@stackables/bridge-core": patch
4+
---
5+
6+
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.

packages/bridge-compiler/src/execute-bridge.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export type ExecuteBridgeOptions = {
8383
export type ExecuteBridgeResult<T = unknown> = {
8484
data: T;
8585
traces: ToolTrace[];
86+
/** Compact bitmask encoding which traversal paths were taken during execution. */
87+
executionTrace: bigint;
8688
};
8789

8890
// ── Cache ───────────────────────────────────────────────────────────────────
@@ -338,5 +340,5 @@ export async function executeBridge<T = unknown>(
338340
} catch (err) {
339341
throw attachBridgeErrorDocumentContext(err, document);
340342
}
341-
return { data: data as T, traces: tracer?.traces ?? [] };
343+
return { data: data as T, traces: tracer?.traces ?? [], executionTrace: 0n };
342344
}

packages/bridge-core/src/ExecutionTree.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ import {
6363
matchesRequestedFields,
6464
} from "./requested-fields.ts";
6565
import { raceTimeout } from "./utils.ts";
66+
import type { TraceWireBits } from "./enumerate-traversals.ts";
67+
import { buildTraceBitsMap, enumerateTraversalIds } from "./enumerate-traversals.ts";
6668

6769
function stableMemoizeKey(value: unknown): string {
6870
if (value === undefined) {
@@ -145,6 +147,17 @@ export class ExecutionTree implements TreeContext {
145147
private forcedExecution?: Promise<void>;
146148
/** Shared trace collector — present only when tracing is enabled. */
147149
tracer?: TraceCollector;
150+
/**
151+
* Per-wire bit positions for execution trace recording.
152+
* Built once from the bridge manifest. Shared across shadow trees.
153+
*/
154+
traceBits?: Map<Wire, TraceWireBits>;
155+
/**
156+
* Shared mutable trace bitmask — `[mask]`. Boxed in a single-element
157+
* array so shadow trees can share the same mutable reference.
158+
* Uses `bigint` to support manifests with more than 31 entries.
159+
*/
160+
traceMask?: [bigint];
148161
/** Structured logger passed from BridgeOptions. Defaults to no-ops. */
149162
logger?: Logger;
150163
/** External abort signal — cancels execution when triggered. */
@@ -726,6 +739,8 @@ export class ExecutionTree implements TreeContext {
726739
child.toolFns = this.toolFns;
727740
child.elementTrunkKey = this.elementTrunkKey;
728741
child.tracer = this.tracer;
742+
child.traceBits = this.traceBits;
743+
child.traceMask = this.traceMask;
729744
child.logger = this.logger;
730745
child.signal = this.signal;
731746
child.source = this.source;
@@ -761,6 +776,23 @@ export class ExecutionTree implements TreeContext {
761776
return this.tracer?.traces ?? [];
762777
}
763778

779+
/** Returns the execution trace bitmask (0n when tracing is disabled). */
780+
getExecutionTrace(): bigint {
781+
return this.traceMask?.[0] ?? 0n;
782+
}
783+
784+
/**
785+
* Enable execution trace recording.
786+
* Builds the wire-to-bit map from the bridge manifest and initialises
787+
* the shared mutable bitmask. Safe to call before `run()`.
788+
*/
789+
enableExecutionTrace(): void {
790+
if (!this.bridge) return;
791+
const manifest = enumerateTraversalIds(this.bridge);
792+
this.traceBits = buildTraceBitsMap(this.bridge, manifest);
793+
this.traceMask = [0n];
794+
}
795+
764796
/**
765797
* Traverse `ref.path` on an already-resolved value, respecting null guards.
766798
* Extracted from `pullSingle` so the sync and async paths can share logic.

0 commit comments

Comments
 (0)