Skip to content

Execution recording: persist runs, list/detail endpoints and the portal viewer #208

Description

@07prajwal2000

Why

routes.record_execution is the switch for keeping a run in our database so it
can be replayed in the portal — as opposed to tracing_enabled, which ships
spans to the project's own OTEL backend and stores nothing here (#195).

Today the flag reaches the compiled artifact and nothing consumes it. There is
no table, no endpoint, and no viewer, so turning it on does nothing at all.
The toggle itself is tracked in #207; this issue is everything behind it.

What

Storage. Two tables, shape sketched in .agents/sessions/195-session-2.1-telemetry-worker.md:

  • trace_runs — project, route, routeVersion, start/end, outcome,
    dropped/truncated state, parentRunId for async custom-block runs.
  • trace_spans — run, seq, parentSeq, blockId, blockType, nullable
    customBlockId, timestamps, outcome/error, truncated payload.
  • Indexes: (projectId, startedAt desc) on runs; PRIMARY KEY (run_id, seq) on
    spans. That PK is also the dedup key for at-least-once delivery and doubles as
    the (runId, seq) index — do not add a second one.

customBlockId is nullable on purpose: spans recorded inside a custom block
carry that graph's node ids, so overlaying them on the route canvas highlights
nothing unless the viewer knows which graph a span belongs to.

Consumer. A second durable consumer on FLUXIFY_TRACES. The stream is
already RetentionPolicy.Limits rather than Workqueue specifically so a
second consumer can exist alongside the OTLP exporter — see #195.

Endpoints. Project-scoped, ACL enforced:

  • list runs for a project/route, paginated, filterable by outcome and time.
  • single run detail with its spans.

Viewer. apps/portal only — apps/web is legacy, leave it. The
/:projectId/executions page is a ComingSoon stub today and is the natural
home.

Notes

  • Runs with endedAt IS NULL older than the route timeout render as
    "incomplete". No sweeper job and no abandoned status writes — a status
    nobody reads is not worth a background job.
  • Retention needs an answer before this ships: recordings are debug data and
    should not grow without bound.
  • The trace payload shape is produced by the request worker (session 1 of Admin telemetry worker: trace transport, JetStream stream and span consumer #195);
    this consumer reads the same TraceRunPayload, so it should land after that.
  • routeVersion on the run header is the only forward-compatibility hook for
    route versioning; keep it.

Blocked on #195 (payload producer). Toggle UI in #207.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions