Changelog
All notable changes to AuditTrail are documented in this file. This is the canonical, always-current changelog. Recent releases are summarised by their user-facing headline; the full per-release detail for v1.0.x is preserved at the bottom.
The live deployment is currently v3.47.0.
[3.47.0] — 2026-07-13
- Docs & blog renderer upgraded to
next-mdx-remotev6. The MDX renderer behind every docs page and blog post moves from v5 to v6.0.0, closing the one remaining known dependency advisory —npm auditis now clean across the workspace. Rendered output is unchanged and was re-verified end-to-end on the built image: Shiki code highlighting, heading anchors, GFM tables and callouts all render identically, and literal route templates like{trace_id}in the API reference are untouched (v6's new MDX-expression blocking applies toformat: "mdx"content; these pages renderformat: "md"and keep the v5 behaviour explicitly). - Web image build fix that came with it: npm places
next-mdx-remote@6in the workspace-localapps/web/node_modulesrather than hoisting it, so the web Dockerfile now carries the workspace-local install into the build stage.
[3.46.0] — 2026-07-13
- OTLP ingest now accepts gzip bodies (real bug fix). The OTel
Collector's
otlphttpexporter — and the OTel SDK's own OTLP/HTTP exporter — compress with gzip by default, but/api/v1/ingest/otlpparsed the raw bytes and returned a confusing 400. That meant the Collector-bridge recipes documented since v3.44.0 (OpenTelemetry, Langfuse) only worked with a manualcompression: noneoverride. The endpoint now transparently decompressesContent-Encoding: gzipbodies, with bounded decompression (32 MiB ceiling → clean 413) so a zip bomb can't exhaust memory. Found by a real end-to-end run of the documented recipe; three pinned regression tests. - LlamaIndex integration guide. New
LlamaIndex page covering the honest
path — OpenLLMetry (
traceloop-sdk) auto-instrumentation → OTel Collector (encoding: json) → OTLP ingest — validated end-to-end with keyless Mock models. Documents the real-world gotcha thatTraceloop.init()silently skips LlamaIndex onllama-index-core-only installs (callLlamaIndexInstrumentor().instrument()explicitly), and that one query lands as several traces (one per LlamaIndex workflow). LlamaIndex joins the landing marquee as the seventh integration chip. - REGO policies concept page. New REGO policies page for the Governance 2.0 surface: what the built-in REGO subset evaluates, the external-OPA path, the full REST table, and an explicit honesty section — stored policies power the on-demand simulator; automatic enforcement at ingest remains the YAML constitutional governor.
- Go SDK module path finalised. The Go SDK's module path is now
github.com/Partha-dev01/AuditTrail/packages/sdk-go(the monorepo subdirectory-module form; releases tag aspackages/sdk-go/vX.Y.Z). This resolves the last SDK-publishing naming blocker — no separate GitHub org is needed. - Demo chat history healed. Demo login now removes malformed chat sessions (user messages with no assistant reply — an old seeding artifact) and seeds one canonical example conversation, idempotently.
[3.45.0] — 2026-07-12
- UI/UX validation sweep + docs truth-up. A three-track live-production
UX review (desktop walk, mobile + docs-site UX, docs-content-vs-code audit)
confirmed the mobile fixes and the v3.44.0 regressions hold (0 console
errors on every route at 1440×900 and 390×844; "All time" traces
default and the Refresh refetch verified live). Everything it found ships
fixed here:
- Docs ⌘K search is clickable again: the search dialog rendered inside
the docs sidebar's sticky stacking context, so the page content painted
on top of the results and mouse clicks fell through to the article
behind the modal (keyboard selection still worked). The dialog now
portals to
<body>. - Analytics ▸ Tool Usage counts successes correctly: the aggregation
only counted spans with status
okas successes, so fleets whose tool spans carrycomplete(HTTP-ingested/seeded) showed a contradictory 0.0% success rate and a frequency chart that only drew tools with errors.complete/successnow count as successes (with a pinned regression test). - Landing hero CTA markup: the hero "Get Started →" was the last
remaining
<a><button>nesting — now the same animated-link pattern the closing CTA adopted in v3.44.0. - Compliance tiles labelled: each policy-group tile pairs a rules-passing count with an eval-level pass rate; both now carry labels ("X/Y rules pass" / "eval pass rate") so the diverging numbers no longer read as a contradiction.
- Assistant
settings_urlfixed: the 412 "no provider key" response pointed at/settings/ai-assistant, which 404s — now/settings?tab=ai-assistant(the docs pages carrying the same path are corrected too). - Docs truth-up across 14 pages: the API reference header version
unpinned from v3.20.0 and its OTLP callout + gateway
supportedhint list brought current; architecture.md rewritten against reality (Caddy not nginx, SQLite WAL in production not PostgreSQL, real/api/v1/ingest/traces+/api/v1/ablation/runpaths,eventWS envelope, the real structured rule-YAML schema, current dashboard surfaces); live-fleet endpoints corrected to/fleet/snapshot+/fleet/topologywith thewindow_minutesquery parameter (the documentedFLEET_WINDOW_MINUTESenv var never existed); the OpenAI/Anthropic integration pages now point at the gateway's real upstream-key sources (env vars / agent secrets) instead of the Assistant-only BYOK table; assistant tile table matches the nine kinds the backend actually emits;AUDITTRAIL_DATABASE_URLvs the Alembic-only unprefixedDATABASE_URLdisambiguated; deployment guide'sAPI_INTERNAL_URLdocumented as the build-time ARG it became in v3.37.0, and its security checklist de-nginx'd; SAE candidate-traces example uses the current model catalog; DAG badge/type-color reference matches the shipped components. - Docs additions:
GET /api/v1/admin/databasejoins the API reference's Admin table; the Python SDK'saudittrail scaffold/audittrail listCLI commands are documented; the tried-and-tested deep-search-agent walkthrough is now on the in-app quickstart; the GitHub docs index lists all 37 pages (the six integration guides, Concepts and the SDK parity matrix were missing).
- Docs ⌘K search is clickable again: the search dialog rendered inside
the docs sidebar's sticky stacking context, so the page content painted
on top of the results and mouse clicks fell through to the article
behind the modal (keyboard selection still worked). The dialog now
portals to
[3.44.0] — 2026-07-12
- Validation sweep + audit fixes. A four-track delta audit (backend,
frontend/docs, dependencies, live E2E) of everything shipped since v3.11.0
found no critical issues; everything it did find ships fixed here:
- Gateway streaming honesty: a streamed proxy call that fails mid-flight
(upstream error event, client disconnect) is now persisted as an error
span with the upstream message — previously every streamed span was
recorded
complete. The detached span-write task is now strongly referenced so it can't be garbage-collected mid-write, and the stream logger reassembles SSE lines split across network chunks so the captured output text is complete. - Gateway
finish_reason: non-streaming Anthropic responses now mapstop_reasonto the OpenAI vocabulary (max_tokens→length, …), matching the v3.43.0 streaming translator; mock completions report a realtotal_tokens. - Compliance math: the legacy
GET /api/v1/evaluations/summarynow uses the same disjoint severity buckets as/constitutional/summary(pass = total − amber − red); its buckets previously double-counted amber rows and didn't sum to the total. - Migration parity guard: a new regression test asserts
alembic upgrade headreproduces the exactcreate_allschema, so model/migration drift now fails CI instead of accumulating silently. - Docs sidebar completeness: the sidebar (desktop + mobile drawer) is now derived from the docs index itself — 11 published pages (pause guards, alerts, compliance, the secrets CLI, the local runner, …) had been missing from it.
- OTel/Langfuse recipes corrected: the Python examples prescribed
OTEL_EXPORTER_OTLP_PROTOCOL=http/json, which the Python OTel SDK does not implement — both pages now bridge Python through an OpenTelemetry Collector (otlphttpexporter withencoding: json) and scope the env-var path to JS/Node, where it actually works. - Smaller fixes: the /traces Refresh button now refetches the list it
sits above (it invalidated the wrong query key); the landing closing-CTA
no longer nests a button inside a link; the WebSocket event union matches
what the backend actually emits; a dead
/livelink in Concepts now points at/fleet. - Dependencies: PyJWT floor raised to 2.13.0 (closes five upstream CVEs,
none reachable in AuditTrail's HS256-only usage), python-multipart to
0.0.32;
npm auditfindings drop from 14 to 1 (the remainder is the knownnext-mdx-remotemajor bump, mitigated by author-controlled content), and a project-wide postcss override enforces ≥ 8.5.10.
- Gateway streaming honesty: a streamed proxy call that fails mid-flight
(upstream error event, client disconnect) is now persisted as an error
span with the upstream message — previously every streamed span was
recorded
[3.43.0] — 2026-07-11
- Gateway: native Anthropic streaming. Streaming requests for
claude-*/anthropic/*models through the gateway proxy now stream natively: Anthropic's Messages SSE events are translated frame-by-frame into OpenAIchat.completion.chunkevents, so tokens arrive incrementally instead of the previous downgrade (the whole completion was buffered and delivered as a single event at the end). The terminal chunk carries a mappedfinish_reason(end_turn→stop,max_tokens→length,tool_use→tool_calls) plus an OpenAI-shapeusageobject, and upstream error events surface as a502instead of being swallowed. With this, every gateway provider streams natively. Streamed Anthropic output text is now also captured on the logged gateway span (the old single-event shape was invisible to the stream logger). Keyless dev environments now get the same scripted mock stream for Anthropic models that other keyless providers serve.
[3.42.0] — 2026-07-06
- Traces list "All time" filter. The traces list time-range filter now offers an All time option and defaults to it, so runs older than the previous 30-day maximum (including long-abandoned still-"running" traces) are visible in the list out of the box instead of showing "No traces found". Previously the list capped at "Last 30d" with no way to see older runs, even though the Overview page's "Recent Traces" (which applies no time window) still listed them — a confusing mismatch. The windowed options (last hour / 24h / 7d / 30d) are still one click away. Analytics and Compliance keep their own 7-day default and are unchanged.
[3.41.0] — 2026-07-05
- Java SDK Maven namespace. The Java SDK's Maven coordinate now uses the
io.imaginaerium.audittrailgroup id (verifiable on theimaginaerium.indomain) instead of the placeholderio.audittrail, so the artifact can be published to Maven Central under a namespace we can prove ownership of. Only the publish coordinate changed — the Java source package and API are unchanged, so existing imports and code are unaffected.
[3.40.0] — 2026-07-05
- Landing page hydration fix. The "Live Fleet" bento tile's mini-radar no
longer triggers a React hydration mismatch (
#418) on the landing page. Its scan-line animation was gated on an in-view flag that initialised differently on the server (visible) than in the browser's first render (hidden), so the server HTML and the client's first paint disagreed — causing a brief visual flash of the scan line and a needless client-side re-render of the tile. The flag now initialises identically on both sides; the animation still starts when the tile scrolls into view. Console is clean on the landing page.
[3.39.0] — 2026-07-05
- Multi-provider routing beyond the gateway. The named OpenAI-compatible
providers (Ollama, Gemini, Qwen) are now usable across every server-side
LLM surface, not just the gateway proxy: the natural-language explainer, the
eval LLM-as-judge, the prompt optimizer, chat-session titles, the ablation
tool-selector, and the BYOK assistant. Each surface picks its provider from
the model id and reads a per-purpose key
(
AUDITTRAIL_<PURPOSE>_<PROVIDER>_KEY→AUDITTRAIL_<PROVIDER>_KEY), so a Gemini/Qwen key — or a keyless Ollama base URL — dropped into a purpose makes that surface run on that provider. OpenAI and Anthropic stay the defaults, and each surface still falls back to its honest template/heuristic when nothing is configured (never a fabricated result). - Ablation keys are namespaced. The ablation tool-selector now passes an
explicit AuditTrail-namespaced key to its provider client instead of relying
on the stock
OPENAI_API_KEY/ANTHROPIC_API_KEY, so ablation can run on a different provider/account than the rest of the stack. - Docs fix. The gateway virtual-key revoke endpoint is documented correctly
as
POST /api/v1/gateway/keys/{id}/revoke.
[3.38.0] — 2026-07-05
- Gateway multi-provider routing. The gateway proxy now routes by model id
to several OpenAI-compatible providers from a single deployment — OpenAI stays
the default, and Ollama, Google Gemini, and Alibaba Qwen are
reachable by prefixing (or naming) the model (
ollama/llama3.2,gemini-2.0-flash,qwen-plus). Each provider has its own base-URL and key environment variables (AUDITTRAIL_GATEWAY_<PROVIDER>_BASE_URL/_KEY, falling back toAUDITTRAIL_<PROVIDER>_*), and all of them stream natively. Ollama needs no key — point the gateway at a local Ollama and route with anollama/prefix. OpenAI (including the existing MiniMax base-URL override) and Anthropic behave exactly as before, and the honest "unrecognised model + a key configured →400, never a fabricated completion" contract is preserved.GET …/v1/modelsonly advertises a non-OpenAI provider once it is actually configured.
[3.37.0] — 2026-07-05
- Go SDK delivery reliability. The Go SDK now re-queues a span batch that
fails a transient flush (network error, or a 5xx after retries) at the front
of the buffer instead of dropping it, so a temporary outage no longer loses
spans. The buffer is bounded (
MaxQueue, default 10000 spans) so a persistently unreachable endpoint cannot grow memory without limit, and a 4xx (permanent rejection — bad key or malformed payload) is now logged to stderr rather than swallowed silently. - Local full-stack compose fix. The web image now bakes the
/api/*rewrite destination from anAPI_INTERNAL_URLbuild arg. Next.js 16 evaluatesnext.config.tsrewrites()at build time, so the previous runtime environment variable was ignored and a localdocker compose upcould not reach the API. Production was never affected — Caddy routes/api/*straight to the api container.
[3.36.0] — 2026-07-05
- Database health (superadmin). A new read-only Database page in the
/adminconsole reports SQLite health at a glance: the on-disk database, WAL and shared-memory file sizes; page count / page size / free-page count; journal mode and WAL autocheckpoint; and a per-table row-count table ("tables by row count"). It is superadmin-only and strictly read-only — derived fromos.stat, read-onlyPRAGMAs andSELECT count(*), never a mutating PRAGMA — and served by a newGET /api/v1/admin/databaseendpoint. It degrades honestly: sizes show as em dashes for an in-memory database, and on a non-SQLite backend the SQLite-specific fields are omitted while the row counts still work.
[3.35.0] — 2026-07-04
- Pause-guard library (Python SDK). A new opt-in module
audittrail.pause_guardsships four composable guards —BudgetGuard,LatencyGuard,ConstitutionalGuardandHumanApprovalGuard— plus aGuardSetand anasync guarded_stepcontext manager. You wire the guards you want around an agent step; when a wired guard trips, the library drives the existing pause round-trip (register → block until the operator resumes → return the possibly-edited state). Guards never auto-pause: constructing one fires zero HTTP, and nothing pauses unless you wire it and callrun(...). Honest by design — budget/latency read caller-supplied measurements (the SDK keeps no cost/token tally) and the constitutional guard takes a user predicate (there is no SDK-local governor). A tripped guard fails safe on its own evaluation errors: budget/latency default to fail-open (proceed), constitutional and human-approval default to fail-closed (pause for review). Full details: Pause guards. SDK version 3.7.0. - New
audittrail.pause_hooks.request_pause_and_wait. The guard library drives its pause through this helper, which unconditionally registers an active pause row and then reuses the same heartbeat / long-poll / by-id terminal read ascheck_pause— all pause transport stays in one place. A guard-initiated pause can carry apause_ttl_secondsso the server reaper bounds its lifetime.
[3.34.0] — 2026-07-04
- Go & Rust SDK parity — OpenAI/Anthropic wrappers + pause hooks. The
Go and Rust SDKs, previously core-client-only, gain provider wrappers
and the pause/resume primitive, closing the biggest gaps in the
SDK parity matrix. In Go the wrappers are
http.RoundTrippers you install on the provider SDK's HTTP client (option.WithHTTPClient(audittrail.OpenAIHTTPClient(audit))); they auto-parent off the trace context on the outgoing request. In Rust the wrappers are generic over anySerializerequest/response pair, so they work with async-openai'screate_byotor any typed client. Both extract model, token usage andgen_ai.*attributes and emit one span per call.Pause(Go) /pause(Rust) mirror the Python/TS hooks: register → heartbeat → block until the operator resumes. - New
GET /api/v1/pauses/{pause_id}. The active-pause poll deliberately excludes terminal rows, so a resumed pause simply disappears from/pauses/{trace_id}/active. This by-id read surfaces the terminal state (resumed / expired / abandoned) and the operator's edited blob — it's how every SDK's pause hook now learns the outcome. A latent bug is fixed alongside: the Python and TypeScript pause long-polls could never observe a resume against a real server (they watched the active endpoint, which never shows a resumed row) — they now read the final state by id.
[3.33.0] — 2026-07-03
audittrail secretsCLI. The Python SDK's console script gains asecretscommand group —list,add,rotate,rm— managing the server-side gateway provider secrets from a terminal. The security contract matches the API's write-only design: values are read from a hidden interactive prompt or--value-stdin(there is deliberately no--valueflag, because argv is visible to every process on the machine), never echoed or logged, andrmrequires typing the secret's name back (--yesfor scripts).listshows metadata andlast4only — a stored value can never be retrieved, only rotated. SDK version 3.5.0; docs: the gateway-secrets page gains a CLI section and the SDK parity matrix a new row.- Docs honesty: API-key auth is
Authorization: Beareronly. The API reference (and two backend docstrings) claimed anX-API-Keyheader worked as an alternative — no code path has ever read that header; every SDK sendsAuthorization: Bearer sk-at-…. The false claim is removed.
[3.32.0] — 2026-07-03
- Live trace completion over WebSocket. Span ingest already streamed
span_start/span_endinto each trace's WebSocket room; the server now also broadcasts atrace_completeevent the moment the last running span closes and the trace transitions tocompleteorerror. The trace-detail view (which already listened for this event) flips the status badge and final aggregates live, without a refresh. Both HTTP and OTLP ingest fire it, the broadcast is best-effort (a WebSocket failure never breaks ingest), and a streamingrun_langgraphrun gets exactly one event at the true end of the run. - Docs. The API reference's WebSocket section now documents the live
streaming events precisely —
trace_completehad been listed in the frontend event union but was never actually emitted by the server.
[3.31.0] — 2026-07-03
- Fleet-level WHY on /analytics. A new Fleet WHY tab shows the
tool-selection surrogate's global feature importances — what drives
tool choice across the whole deployment (mean |SHAP| over the
training set, with an XGBoost-gain fallback), alongside the
training-set tool mix, sample count and the model's F1. Served by
GET /api/v1/analytics/fleet-why; when the model hasn't trained the response says so instead of fabricating importances. - Time-to-WHY cold start: no restart needed. The surrogate used to train only at API startup, so a fresh install that ingested its 5th tool span while running showed heuristic XAI values until the next restart. A background pass now trains the model automatically as soon as enough data exists (at least 5 tool spans with two distinct tools). Single-tool fleets are declined gracefully — a classifier can't learn from one class — instead of erroring on every pass.
- Heuristic SHAP results are now labelled as such. The SHAP
feature-importance cards on
/sankeyand the trace-detail Sankey tab flag heuristic (untrained-at-compute-time) results with an explicit "Heuristic estimate" note and no longer present the fallback's fabricated F1 as a real fidelity score; the low-fidelity warning banner now applies only to genuinely learned attributions. - Analytics honesty. The agent filter pill on
/analyticswas a dead control (hardcoded names, never applied to any query) — removed.
[3.30.1] — 2026-07-03
- Real SHAP values restored on trained surrogates. Modern SHAP
returns a 3D array for multiclass models where older releases
returned a list of per-class arrays; the explainer choked on the new
shape and silently fell back to its heuristic values even when the
surrogate was fully trained. Ablation SHAP cards and action evidence
now report genuinely learned attributions (labelled
surrogatewith the model's F1) whenever the model is trained. - Honest fallback wording. When attribution does fall back to heuristics, the explanation now states the actual reason — an untrained model and a SHAP-explainer failure are different situations and are reported as such.
- Stable demo evidence anchors. The demo queue's evidence rows now anchor to stable seeded traces instead of the always-reseeded fleet activity burst, so their "Inspect span" links no longer go stale after the next demo login; rows whose anchor trace has been purged are re-anchored automatically.
[3.30.0] — 2026-07-03
- Evidence-carrying actions. A deployment-action proposal that
targets a trace or span now carries attribution evidence computed at
propose time — the no-LLM surrogate SHAP attribution for the
decision-relevant tool span (preferring a span flagged amber/red by
the governor), with a deterministic English explanation, the top
feature weights, the violated rule when one triggered the concern,
and a one-click jump into the trace's XAI tabs. The
/deploymentsapproval drawer renders it as a "Why this action" panel, so approvers see evidence instead of rubber-stamping a reason string. Provenance is honest: results are labelledsurrogate(with its F1) only when the model is really trained,heuristicotherwise; proposals without a resolvable trace/span target state plainly that no span-level evidence exists. Assistant-proposed actions show the same explanation line in chat.
[3.29.0] — 2026-07-03
- Eight new docs pages. Six per-integration guides — LangChain, LangGraph, OpenAI, Anthropic, OpenTelemetry and Langfuse — each with install steps, verified wiring snippets and honest notes on which paths are in-process vs remote. Plus a Concepts page (the trace → span → governor → WHY-pipeline → control-plane mental model on one page) and an SDK parity matrix that states plainly what each of the six languages ships today.
- The landing framework marquee's chips now link to these per-integration guides instead of interim targets.
[3.28.0] — 2026-07-02
- Docs are readable on phones. Below tablet width the docs sidebar collapses into a drawer behind a hamburger in a new sticky top bar — it used to render as a fixed full-width column that squeezed the article to a one-word-per-line sliver on a 390px screen.
- Dashboard pages no longer overflow sideways on mobile. The content column let any wide element (the traces table, the trace-detail tab strip) stretch the whole page past the viewport — the body was 731px wide on a 390px phone. Wide tables now scroll inside their own cards, the trace-detail tab strip scrolls in place, and the summary cards and span rows stay fully on-screen.
- One Filters button on mobile traces. The filter bar's four
dropdowns collapse into a single Filters button (with an active-filter
count) that opens a bottom sheet with labeled controls — previously
three unlabeled dropdowns dominated the viewport with the third
clipped off-screen. The dropdowns also finally display "All agents" /
"All envs" / "All models" / "Last 24h" instead of the literal
placeholder
_all_.
[3.27.0] — 2026-07-02
- Landing page closes with a call to action again. A slim single-band CTA ("Ship agents that explain themselves." with Get Started and View Demo) sits between the FAQ and the footer — the old multi-panel Finale stays retired, but the page no longer dead-ends after the FAQ.
- The framework marquee only advertises real integrations. Trimmed from 12 logos to the 6 with an actual path today — LangChain, LangGraph, OpenAI, Anthropic, OpenTelemetry, and Langfuse — and every chip is now a link to its integration guide. The scroll pauses on hover or keyboard focus so the chips are clickable. The removed logos return as their per-framework guides ship.
- Less dead space mid-page. The before/after comparison card no longer stretches to 648px with an empty bottom third (height capped), and the vertical gaps around the platform-comparison table are roughly halved — no more full blank viewports while scrolling at laptop heights.
- The before/after slider starts at 65% so the causal-attribution panel's column header isn't sliced mid-word before you touch the slider.
[3.26.0] — 2026-07-02
- Settings General tab now tells the truth. The About card reports the API's real version, environment, rules directory, and credential-masked database path — it used to hardcode a version string. Performance metrics are real: API p95 is measured from a rolling window of actual request durations (shown as "measuring…" until enough samples exist) instead of numbers derived from uptime, and Loaded Rules counts the YAML rules the governor actually evaluates. The dead Environment/Log-level/Rules-directory/DB-path form controls — whose Save silently failed with a 422 on every click — are gone; Preferences now contains exactly the settings the API accepts.
- Compliance page no longer contradicts itself. The donut, group
tiles, and rules table now describe the same data: severity buckets are
disjoint (pass + amber + red = total; amber rows were previously
double-counted as both a pass and a warning), group tiles resolve each
rule to its real YAML policy group with a real per-group rule count
(previously every evaluation lumped into Safety with a hardcoded rule
count of 1, rendering "10/1 pass"), the latency-SLO rule's
performancegroup maps to Quality, and the per-rule 7-day trend sparkline is computed from real daily aggregates instead of repeating the all-time rate seven times. Demo evaluations now reference the real YAML rules; demo users carrying evaluations with orphaned rule ids are healed on their next demo login. - One filter system on /traces. The redundant second row of filter
pills (a hardcoded 3-agent list and a time pill that never filtered
anything) is gone. The global filter bar's agent / environment / model /
time-range selections now all actually apply to the trace list, and the
Sankey "tool" click-to-filter chip filters for real via a new
toolquery parameter onGET /api/v1/traces. - Deployments queue stays fresh and actions never clip. Seeded demo action timestamps re-anchor on login (the queue used to show dates from whenever the demo user was first created), and the Actions column has a minimum width with wrapping buttons.
- Assistant errors survive reload. If the provider fails mid-stream (for example an HTTP 429), the turn is persisted with an honest error note — reloading the chat used to show your question with no reply at all.
- Analytics and compliance time-series charts add right margin so the last axis label isn't clipped.
[3.25.0] — 2026-07-02
- SAE extraction path fixed — it now actually runs. The self-host
mechanistic-XAI path had never been runnable end-to-end: the adapter
imported a nonexistent
saelensmodule (the real package imports assae_lens), the documentedaudittrail[sae]extra didn't exist in any package, the activation hook only fit Llama-shaped models, and the Mistral catalog entry pointed at a GPT-2 SAE. Extraction is rewritten on TransformerLens (HookedSAETransformer+SAE.from_pretrained) and validated end-to-end on real weights: gpt2-small → 12 features, all 12 labeled by Neuronpedia. - Honest supported-model catalog.
gpt2-small(a.k.a.gpt2) joins as the smallest entry point — CPU-only, ungated, Neuronpedia-labeled. Gemma-2-2b moves to the canonical GemmaScope release; the Llama entry is corrected tometa-llama/Meta-Llama-3-8B-Instruct(the model its published SAE was actually trained on); Mistral-7B is removed (it has no published SAE). - Per-model HF-key gating.
AUDITTRAIL_SAE_MODEL_KEYis now only required for license-gated base models (gemma, llama) — gpt2-small needs no key. The span-state API exposeshf_key_requiredand the pre-flight checklist marks the key row "not required for this model". - Install copy corrected everywhere:
pip install sae-lens(and a real[sae]extra now exists on the API package for source checkouts).
[3.24.0] — 2026-07-02
- Automatic model pricing. Cost estimation no longer depends on a
hand-maintained 18-model table. Known models (~3k: GPT, Claude, Gemini,
DeepSeek, Llama, …) now price automatically from a bundled community
catalog generated from LiteLLM's price index, including dated variants
(
gpt-4o-mini-2024-07-18) and provider-prefixed ids (openai/gpt-4o). The catalog auto-refreshes from upstream every 24 hours (AUDITTRAIL_PRICING_AUTO_REFRESH, default on) so new models cost correctly without a redeploy; a failed refresh keeps the current catalog, so air-gapped installs simply run on the snapshot. - Resolution order: agent-supplied cost →
rules/pricing.yamloperator overrides (substring, longest key wins) → community catalog (exact/normalized match) → default rate (badged "Default Rate").rules/pricing.yamlslims down to a pure override file — negotiated rates and zero-cost self-hosted models. GET /api/v1/settings/pricingnow reports the catalog layer (entry count, snapshot vs live refresh, last refresh time) alongside the operator table. The prompt-optimizer cost pre-flight consults the same tables instead of its own hardcoded mirror.
[3.23.0] — 2026-07-02
- Sign-in polish. The auth card's primary-tinted drop shadow rendered as a detached glowing slab under the card on the dark backdrop; replaced with a neutral elevation shadow.
- Reports page rework. The generator now mirrors the generate endpoint's real contract: the always-included PDF sections (executive summary, trace statistics, compliance summary) render as fixed rows, and only the two real toggles (violation details, cost & latency analytics) remain interactive. The previous "Report Type" selector and "Full View" checkbox were never sent to the API and have been removed. Completed-report badges now use the status color system, and the page gained a description header.
- Sidebar. The Live connection pill moved to the very bottom of the sidebar so it reads as ambient status rather than a nav item.
- Span pickers show names. The Counterfactuals and SAE tab span pickers displayed the raw span UUID in the closed trigger; they now show the span name (with a truncated id for disambiguation).
- Trace-detail tab fallback. An unknown
?tab=value in a trace-detail URL now falls back to the Spans tab instead of rendering an empty panel.
[3.22.0] — 2026-07-02
- Honest health signal.
GET /api/v1/healthrules_loadednow reports the YAML constitutional rules the governor actually evaluates (loaded at startup) instead of a legacy database table that read0on a healthy instance. - The demo
/alertspage tells a story. Demo-login now seeds two alert rules (p95 latency, cost burn rate) plus two historical firings, so the alerts surface renders real content instead of an empty state. - Correct pagination under sort.
GET /api/v1/tracesrejects a paginationcursorcombined with anysort_byother thancreated_at(HTTP 400) — the keyset cursor only produces correct pages in creation order. - Faster trace detail. The Sankey, Counterfactuals, SAE, and Raw tabs on
/traces/{id}now load lazily, keeping d3 and the syntax highlighter out of the page's initial JavaScript until a tab is opened. - SQLite robustness. The API sets
busy_timeout=5000andsynchronous=NORMAL, so concurrent writers wait instead of failing with database is locked. Newix_spans_modelindex (Alembic 020) serves the model filter and SAE candidate discovery. - Readable quickstart download button. The starter-template "Download ZIP" button on the quickstart page rendered its label cyan-on-cyan (the docs prose link colour out-specified the button text class). Also refreshed two stale copy spots: the landing FAQ's "cloud tier coming in v3.0" and the version literal in the quickstart health-check sample.
[3.21.0] — 2026-07-02
- SAE pre-flight + "which runs support SAE?" discovery. The SAE tab now
shows a consolidated four-gate pre-flight checklist (model has a published
SAE ·
saelens+torchinstalled · Hugging Face key set · activations cached) so you see every prerequisite at once instead of one-at-a-time. The panel is now explicit that SAE is a self-host capability — extraction runs on your own API host with theaudittrail[sae]extras and an open-weight model; the hosted demo doesn't run it. A newN SAE-readybadge on/traces(backed byGET /api/v1/sae/candidate-traces) tells you which of your runs even could run SAE — honestly0on the closed-weight demo.
[3.20.0] — 2026-07-02
- Counterfactuals that actually run. Clicking Counterfactuals (or SAE) on
a tool span used to fail with "span has no prompt text" — the tool span was
keyed by its argument (
calculator(expression=…)), not the user prompt. Resolution now walks up to the orchestrator span to recover the real user prompt, so leaf tool spans work. Empty results are split into three honest states — stable (your tool choice is robust), surrogate not trained yet, and surrogate disagrees with the observed choice — instead of one ambiguous card. Counterfactuals also make no LLM call and carry no cost estimate, and every candidate now ships a plain-languageexplanationandpredicted_outcome. - One-click launch + deep links. The DAG node panel now links straight to
the Counterfactuals tab (tool spans) or SAE tab (LLM spans) with the span
preselected (
?tab=…&span=…), and both tabs are deep-linkable.
[3.19.0] — 2026-07-01
- Sign-in & sidebar polish. Refined the sign-in backdrop so the footer no longer reads as a detached tier, widened the dashboard sidebar (272 → 300px), and moved the navigation scrollbar flush to the sidebar edge.
[3.18.0] — 2026-07-01
- Schema formalisation. Added Alembic migrations 018/019 so a clean install or a future Postgres cutover reproduces the full schema through migrations (the live SQLite deployment is unchanged — it bootstraps its schema directly).
[3.17.0] — 2026-07-01
- Security hardening. Raised dependency floors for several CVEs
(
starlette,python-multipart,cryptography,PyJWT,next,react), added a conservativeContent-Security-Policyresponse header, and pinned all CI workflow actions to exact commit SHAs.
[3.16.0] — 2026-07-01
- Documentation truth-up. Brought the API reference current (runtime-control
deactivate, TTLexpires_at, dispatch provenance fields, the gateway400 unsupported_model), un-froze the changelog, and fixed stale copy in the quickstart.
[3.15.0] — 2026-07-01
- Refreshed landing walkthrough screenshots. Regenerated the product-tour images against the live site, including the Causal Attribution panels (05/07) and a populated counterfactual picker.
[3.14.0] — 2026-07-01
- Landing-page trim. Removed the hero stats strip, the pricing teaser, and the finale section to focus the landing narrative.
[3.13.0] — 2026-07-01
- Backend correctness & hardening. The gateway now logs streaming spans on a fresh database session (SSE calls are no longer silently un-logged), the deployment-action executor distinguishes transient from permanent failures so a run that couldn't reach a daemon is retried instead of being marked executed, and the gateway validates the virtual key before reading the request body.
- New end-to-end tests covering the full governance chain (propose → approve → enforce → deactivate/TTL-reap, plus cross-tenant isolation) and the online-evaluation sampler/executor.
[3.12.0] — 2026-07-01
- Frontend audit fixes. The constitutional-violation toast now matches the
real broadcast shape (fires on
amber/redseverities), the runner dispatch/run-output tiles read the correct response fields, and several footer links that 404'd were repaired.
[3.7.0 → 3.11.0] — Sync & Expand (gateway honesty, docs truth-up, runtime-control TTL)
- v3.7.0 — Honest unsupported-model gateway. When a provider key is
configured, an unrecognised model now returns a
400 unsupported_modelinstead of a fabricated mock completion. Agent-registry bundle validation hardened (control-character + edge-whitespace rejection). - v3.8.0 — API-reference truth-up. Both the in-app and GitHub-facing API references were rewritten to the full ~222-endpoint / 40-module surface, and six previously-undocumented surfaces got feature docs (SAML SSO + SCIM, the anomaly alert engine, datasets + eval runs, pause / inspect / edit+resume, EU AI Act compliance, and the runner run-status panel).
- v3.9.0 — Runtime-control TTL. Runtime controls
(
switch_model/throttle/disable_flag) gained an optional expiry (expires_at); a background reaper auto-deactivates expired controls so an expired throttle/disable stops enforcing (fail-safe). - v3.10.0 — Manual deactivate. Operators can deactivate an active
runtime control from the Run Status page
(
POST /deployments/actions/runtime-controls/{id}/deactivate), which also shows each control's remaining TTL. - v3.11.0 — Dispatch version provenance. Registered-agent dispatches now
persist and surface
agent_definition_id/agent_version_id/agent_version, so a run's status and detail show exactly which agent version executed (null for built-in templates).
[3.1.0 → 3.6.4] — Agent Operations Control Plane + launch prep
AuditTrail evolved from observe-only into an honest control plane that orchestrates, versions, rolls out, governs, and kills agent runs — while execution stays on the user's own runners (we do not host execution).
- Runner template allowlist — a two-gate, argv-only dispatch path so the operator can never ship arbitrary code to a connected daemon.
- Agent registry + immutable versioning — register agent code, version it immutably (server-computed checksum), promote a current version, dispatch a pinned version to your own daemon.
- Safe BYOK gateway secrets — store provider keys encrypted at rest, used only server-side in the gateway forward path; the plaintext is write-only.
- Executable deployment actions — approved Tier-2 controls (
switch_model/throttle/disable_flag) are enforced on gateway-routed traffic;kill_runtargets your connected daemon; the rest are honest external handoffs recorded viamark-executed. - Run-status panel — connection state, live dispatch stdout (SSE), and the active runtime controls.
- OpenAI-compatible gateway base-URL override, plus E2E-test-driven fixes
(schema-drift startup bridge, runner-token
/runner/statusauth, live-pill WS routing, footer hydration). Migrations 014–017.
[3.0.0] — Public-launch prep (Phase 7–9)
- Full Edit + Resume for paused agents (feature-flagged, schema-hash guarded; Alembic 012) + heartbeat / ack / reaper lifecycle.
- Local runner daemon —
audittrail daemonships with the Python SDK, connects over WebSocket with ansk-atd-…token, and runs allowlisted agents on your own machine (Alembic 013). - Chat history (Alembic 011), prompt canary with auto-rollback
(Alembic 010), and the Python SDK
AuditTrailClient+run_langgraph. - Security hardening: webhook SSRF guard, governor ReDoS backstop, JWT
exprequired,eval()removed in favor of an AST-allowlisted evaluator./pricing/blogpages, NOTICE refresh, 7 SDK pre-publish CI workflows.
[2.x] — Platform expansion (v1.1 → v2.9)
- v1.1 — TypeScript SDK, OpenAI-compatible gateway proxy, OTLP/JSON ingest, EU AI Act compliance surface, and the anomaly alert engine.
- v1.2 — multi-tenant organizations + members + invites, datasets + eval runs, the prompt lab, projects, and SAML SSO / SCIM provisioning.
- v1.3 — NL explanations, counterfactuals, the prompt optimizer, adaptive model routing, and the deployment-intelligence action queue.
- v2.0 — SAE mechanistic XAI, REGO (Governance 2.0) policies, and the Stripe billing surface.
- v2.2 → v2.9 — Live Fleet, the Operations Assistant (generative-UI tiles + BYOK chat), pause/inspect preview, per-user rule bindings, live cost/latency DAG overlay, the docs site, and the landing-page build-out.
[1.0.2] - 2026-04-08
Comprehensive PRD ↔ codebase sync sprint. Addresses every CRITICAL
and HIGH finding from the 2026-04-07 audit (docs/research/audit-2026-04-07/),
plus a follow-up wave that builds out every previously-deferred PRD line
item and ships the cross-tenant /admin route group.
Sync sprint Phase 3 — previously-deferred PRD items now shipped
- Adaptive ablation strategies (FR-017).
causal.run_ablationnow acceptsstrategy: "linear" | "adaptive" | "hierarchical"(default"adaptive") andruns_per_segment ∈ [1,5]. The adaptive path runs one cheap pass over every segment, then re-scores only segments whose attribution falls in[0.05, 0.30]with the full quota. The hierarchical path buckets prompts with >12 segments into 4 coarse groups, scores those, then drills into the top-2. Both apply early termination once the top-3 segments cover ≥90% of the cumulative attribution. - Sankey segment merge/split editor (FR-025). New segment-editor
component lets users merge adjacent segments or split at a word
boundary before running ablation. Backed by
segments_overrideon/ablation/runand a new/ablation/segments/previewhelper. - DAG semantic 3-level zoom (FR-011) + Dagre Web Worker (NFR-013) for 200+ node graphs.
- Compliance trend deployment markers (US-025) sourced from
audit_logrule events. - Email verification flow (
/auth/verify-email,/auth/resend-verification). - Refresh token rotation with theft detection (migration 004,
/auth/refresh, silent refresh in the api-client). audittrailCLI (rules validate,rules list,bootstrap-superadmin).- Global filter wiring —
useFilterStorecarries phrase + tool,GlobalFilterBarexposes chips,/tracesseeds the store from URL params.
Wave 18 — RBAC /admin route group
- New
auth.require_superadmindependency +routes/admin.pywith 8 endpoints (users CRUD, audit-log, audit-log/export CSV stream, tenants list, instance/info). Self-protection prevents a superadmin from revoking their own flag or deactivating their own account. - Frontend
app/(admin)/admin/route group with layout (auth guard), overview, users, tenants, audit-log pages. - Sidebar gains a superadmin-only "Admin" entry.
audittrail bootstrap-superadminCLI for the fresh-deployment promote-or-create flow.
Demo mode + login polish
- New
POST /api/v1/auth/demo-login(CSRF-exempt, 20/min). Find-or- creates ademo@audittrail.devuser, seeds 10 sample traces with 30 spans on first call, issues normal cookies. Landing page "View Demo" buttons hit this endpoint. useCurrentUserexposesis_superadmin+email_verified. NewformatRoleLabelhelper renders "Admin" / "Viewer" / "Superadmin" consistently across the dashboard sidebar and profile page.- Sign-out button in the dashboard sidebar footer is now an explicit labelled button.
- Verify-email page wrapped in
<Suspense>so Next.js 16 static rendering acceptsuseSearchParams(CI build was failing).
CRITICAL security fixes (cross-tenant data exposure)
- Fixed: WebSocket trace subscription bypass (#115).
routes/ws.pynow verifiesTrace.user_id == authenticated_user.idbefore attaching the connection to the trace's ring buffer; runtimesubscribemessages are also gated. Closes a live cross-tenant exfiltration vector where any authenticated user could replay the last 100 events of any other tenant's trace. - Fixed:
DELETE /api/v1/traces/flush-allglobal wipe (#116). Now scoped toTrace.user_id == calling admin's id; tenant admins can no longer wipe other tenants' traces. - Fixed: Anonymous span ingestion (#117). Both
POST /api/v1/ingest/spansand/ingest/tracesswitched fromDepends(get_optional_user)to a newrequire_user_or_apikeydependency that rejects anonymous calls with 401. Adds explicit cross-tenant guard inside_ensure_trace/_ensure_agent.
HIGH security fixes
- Fixed: Cross-tenant agents endpoints (#118). Added
Agent.user_idvia Alembic 003 + new_visible_to(user)clause used by every read/write inroutes/agents.py. Two tenants can register agents with the same display name without colliding. - Fixed: Sparkline
day_stmtuser_id leak (#119) —routes/analytics.py:233-238now joins/filters by user. - Fixed:
/analytics/slowest-spansp95/p99 leak (#120) — duration query now joinsTraceand filters by user. - Fixed: Global rules / settings (#121). Rules and settings split into instance defaults vs per-tenant overrides. Tenants can no longer disable safety rules platform-wide. See "Per-tenant scoping" below for the full migration.
- Fixed: JWT non-revocable + 24h TTL (#122). Default TTL shortened
to 1 hour (
AUDITTRAIL_JWT_EXPIRY_HOURS).get_current_useralready re-fetches the row each request so role demotion is reflected within the access-token TTL. - Hardened:
seed.pyproduction guard (#123). Now requires bothAUDITTRAIL_DEBUG=trueANDAUDITTRAIL_ALLOW_SEED=1. The opt-in flag is intentionally outside the Pydantic settings so a stale.envcannot accidentally re-seed prod. - Added: per-route rate limits on heavy endpoints (#124).
/ablation/run,/ingest/spans,/ingest/traces,/reports/generatenow carry explicit@limiter.limit("…/minute")decorators in addition to the global default. Concurrent ablation jobs per user are also capped viaablation_max_concurrent_per_user. - Hardened: ReDoS surface (#125). Constitutional rule regex
patterns are screened against a deny-list of catastrophic-
backtracking shapes (
(a+)+,(a*)*,(a|a)+) before compilation. Pattern + input length caps from 002 are kept.
MEDIUM security hardening
- Added:
SecurityHeadersMiddlewareinmain.pyso HSTS, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer- Policy, Permissions-Policy and COOP are present even when no fronting proxy adds them. - Added:
BodySizeLimitMiddlewarerejects requests larger than 5 MiB at the FastAPI layer (defence-in-depth complement to nginx). - Added: AuditLog table (NFR-023, #137). New
audit_logtable +audittrail/audit_log.pyhelper. Every admin mutation (rule.update,rule.create,rule.delete_override,rule.reload,agent.create,agent.delete,settings.patch,webhook.*,report.generate,password.reset_*,auth.logout) records who, when, payload, IP and user-agent. - Widened: PII redaction scope (#135).
_upsert_spannow redactserror_message,attributes, andtool_calls.{arguments,result}in addition tospan.input/output. - Added: Pydantic
extra="forbid"on the new ablation, webhook, and password-reset request schemas to silently reject unknown fields and prevent client-side typos from masking validation failures.
Per-tenant scoping (Wave 5)
- Added: Alembic migration 003 (
003_tenant_scoping_and_audit_log.py). Addsuser_idtoagents,rules; addsuser_settingstable for per-tenant overrides; addsusers.is_superadmin,users.email_verified,users.reset_token, etc.; adds theaudit_logandwebhookstables; addstraces.archived_at/traces.deleted_atfor the lifecycle state machine. - Added: Tenant-scoped rule layer.
app.state.rulesis now the YAML-loaded instance default; per-tenant overrides live inapp.state.tenant_rules[user_id]and shadow defaults at evaluation time.routes/ingest._evaluate_and_broadcastcalls_effective_rules(request, user)so each tenant sees their own effective ruleset during constitutional evaluation. - Added:
UserSettingmodel + tenant-scoped settings reads/writes. Reads merge per-user rows on top of instance defaults; writes always land inuser_settingsfor the calling tenant. - Added: Trace cross-tenant guard in
_ensure_trace. Spans cannot be appended to a trace owned by a different user; the call returns 403 instead of silently mutating someone else's row. - Added: WebSocket tenant filter on
/ws/live. TheConnectionManagerrecords(user_id, is_admin)on each live subscriber and_can_see_tracefilters every broadcast.
Schema management
- Removed: Runtime
ALTER TABLEblock ininit_db()(#100). Schema management is now Alembic-only; the entrypoint runsalembic upgrade headandinit_dbonly ensures fresh-install table creation + WAL mode.
Constitutional governor
- Added: BR-002 duplicate rule-id detection.
load_rulesnow cross-checks IDs across YAML files and skips duplicates with a clear error log naming both source files. - Added: BR-003 amber ≤ red validator. Pydantic
model_validatoronRuleDefinitionrejects rules where the amber threshold sits past the red threshold for a given operator. - Fixed: WS event envelope key drift (#95). The dead-code path in
governor.evaluate_trace_and_broadcastwas emitting"type"instead of"event"; now matches the rest of the codebase.
Causal attribution & SHAP
- Added: Real LLM ablation path (#32).
causal._real_llm_tool_selectionhits Anthropic or OpenAI whenablation_real_llm_enabled=truein settings, falling back to the heuristic on provider failure. - Added: 3× averaging for ablation runs (BR-009 / #36).
run_ablationnow acceptsruns_per_segment(1..5) and runs the scorer inasyncio.gatherparallel for each masked variant before averaging the normalised scores. - Added: Surrogate model warm-train on startup (#33). The lifespan
task now pulls up to 500 recent tool spans, builds a feature matrix,
and calls
SurrogateModel.train()so SHAP responses can return real F1 scores instead of the hard-coded0.85.
Auth & SDK
- Added:
/auth/forgot-passwordand/auth/reset-password(#34). v1.0 implementation logs the reset token to the server console per PRD §15.1; v2.0 will deliver via email. - Added: Frontend
/forgot-passwordpage wired to the backend. - Added:
audittrail.init(frameworks=...)SDK shim (#35). Honors the public API documented in PRD §14.3; warns on unknown framework names; calls through toinit_tracer. - Hardened:
/auth/logoutrecords the action inaudit_logeven for cookies that cannot be decoded.
Webhooks (#43)
- Added: outbound webhook delivery service. New
webhooks.pymodule +routes/webhooks.pyCRUD endpoints. Supports Slack webhooks, PagerDuty Events v2, and generic HTTPS with HMAC-SHA256 request signing. Delivery happens off the request path viaasyncio.create_taskand is retried with exponential backoff. Failures are persisted toWebhook.last_error. - Wired: ingest pipeline fan-out. Constitutional violations now fan out to the trace owner's enabled webhooks at the same severity threshold the user configured.
Frontend
- Removed: Dead
Continue with API Keybutton + Apache license footer link from the login page (#179) — replaced with a "Create an account" link and a working/forgot-passwordlink. - Removed:
components/dashboard/sidebar.tsx(285 LOC dead code). - Removed:
components/trace/trace-table.tsx+trace-columns.tsx(640 LOC dead code). - Removed:
nuqsdependency (declared, never imported). - Added: Sign-out button to the dashboard sidebar footer that
hits
POST /v1/auth/logoutand routes to/login. - Added:
/forgot-passwordpage matching the login UI. - Moved:
tests/capture-screenshots.spec.ts→scripts/so it is not auto-run as part of the e2e suite. - Added:
@audittrail/sharedworkspace dependency inapps/web/package.jsonso the orphaned shared types package can finally be imported by frontend code.
Architecture / deployment
- Removed: in-container nginx service from
docker-compose.yml(#88). Production already uses Caddy on the Lightsail host as the single TLS / proxy layer; the duplicate in-container nginx is now retired.
PRD sync (highlights — see SYNC_AUDIT_REPORT.md for the full diff)
- Documented per-tenant scoping, AuditLog, Webhook, UserSetting in the ERD entity list.
- Removed CrewAI adapter (FR-007b) from the roadmap entirely. The intended adapter never shipped, and PRD §23.8 now reflects that the v1.0 framework matrix is LangGraph + LangChain + AutoGen + raw OpenAI.
- Removed Mermaid Sankey fallback (FR-027a) from the roadmap and from the response schema. The hand-rolled SVG Sankey covers every supported display surface and the Mermaid path was unimplemented prose.
- Added § "Reference Production Topology" describing Lightsail + Caddy + cron-poll deploy.
[1.1.0] - 2026-04-01
Authentication & User Flow
-
Fixed: Register page styling mismatch. The register page used shadcn Card/Button/Input components while the login page used inline styles with
#171717card and#0a0a0abackground. Restyled register to match login exactly (same radial gradient glow, card dimensions, input styling).- File:
apps/web/app/(auth)/register/page.tsx
- File:
-
Fixed: Default email pre-filled on login. Login form had
defaultValues: { email: "priya@company.com" }hardcoded. Changed to empty string so users enter their own credentials.- File:
apps/web/app/(auth)/login/page.tsx
- File:
-
Fixed: Login error silently redirecting to demo. The login catch block was redirecting to
/overview(demo dashboard) on ANY error, including invalid credentials. Now shows an inline error message instead.- File:
apps/web/app/(auth)/login/page.tsx
- File:
-
Fixed: Mock data leaking to real accounts. The
useCurrentUserhook fell back to a hardcoded "Priya S." demo user whenever auth failed (isDemo = isError || !data), causing real registered users to see Priya's profile on page refresh. Rewrote the hook to only activate demo mode when explicitly requested via?demo=truequery parameter. Unauthenticated users are now redirected to login.- Files:
apps/web/hooks/use-current-user.ts,apps/web/app/(dashboard)/layout.tsx,apps/web/app/page.tsx
- Files:
Error Handling
-
Added: Global error boundary.
app/error.tsxcatches unhandled runtime errors and shows a dark-themed retry page.- File:
apps/web/app/error.tsx
- File:
-
Added: 404 page.
app/not-found.tsxshows a dark-themed "Page not found" page with a link back to home.- File:
apps/web/app/not-found.tsx
- File:
User Profile & Identity
-
Added: Dynamic user avatar via
useCurrentUserhook. Created a React Query hook that fetches the authenticated user fromGET /v1/auth/me. Wired into dashboard sidebar, layout header, and profile page to replace hardcoded "PS" / "Priya S." / "AI Engineer" values.- Files:
apps/web/hooks/use-current-user.ts,apps/web/components/dashboard/sidebar.tsx,apps/web/app/(dashboard)/layout.tsx,apps/web/app/(dashboard)/profile/page.tsx
- Files:
-
Added: Platform-aware keyboard shortcut display. Sidebar now shows "Cmd+K" on macOS and "Ctrl K" on Windows/Linux, using a hydration-safe
useEffectpattern.- File:
apps/web/components/dashboard/sidebar.tsx
- File:
-
Added: Keyboard shortcuts hint card on Overview page. Animated card showing 5 key shortcuts (Cmd/Ctrl+K, G->O, G->T, G->C, G->A) with platform-aware modifier key.
- File:
apps/web/app/(dashboard)/overview/page.tsx
- File:
Testing
-
Added: Vitest unit test infrastructure. Installed vitest, happy-dom, @testing-library/react, @testing-library/jest-dom. Created
vitest.config.tswith path aliases.- Files:
apps/web/vitest.config.ts,apps/web/package.json
- Files:
-
Added: Unit tests. 21 tests across 2 files covering
formatDuration,formatCost,formatTokens,truncateId,formatRelativeTime(utils) andApiClientGET/POST/204/error/params handling (api-client).- Files:
apps/web/__tests__/utils.test.ts,apps/web/__tests__/api-client.test.ts
- Files:
API & Schema
-
Fixed: ToolCallInSpan
resultfield validation. Theresultfield inToolCallInSpanexpectsdict | None, not a plain string. Passing a string caused a 422 Unprocessable Entity error. Documented the correct format{"output": "..."}in API reference with a "Common mistake" callout.- File:
docs/api-reference.md
- File:
-
Documented: API response envelope pattern. All GET endpoints wrap responses in
{"data": ...}. Added a note at the top of the API reference warning consumers to readresp.json()["data"], notresp.json()directly.- File:
docs/api-reference.md
- File:
DAG Visualization
-
Fixed: DAG vertical spacing. Dagre layout
ranksepincreased from 60 to 120 pixels,nodesepfrom 40 to 50, margins from 20 to 30. Nodes now have proper vertical breathing room.- File:
apps/web/components/dag/dag-viewer.tsx
- File:
-
Added: Live WebSocket DAG updates. DAG viewer now subscribes to
useTraceWebSocket(traceId)and invalidates the React Query cache onspan_start,span_end, andtrace_completeevents, so the DAG re-renders as spans arrive.- File:
apps/web/components/dag/dag-viewer.tsx
- File:
Ablation Modal
- Fixed: Prompt segments overflow. The ablation cost dialog's prompt segment list overflowed the modal without scrolling. Replaced
ScrollArea(which wasn't respectingmax-h) with a nativeoverflow-y-autodiv with explicit height and thin scrollbar styling.- File:
apps/web/components/sankey/ablation-cost-dialog.tsx
- File:
Sankey Diagram
-
Fixed: Hardcoded tools replaced with dynamic extraction. The ablation engine hardcoded
["web_search", "calculator", "read_file"]as tool candidates. Now queriesToolCallrecords and tool-type span names from the actual trace to build the candidate list dynamically. Works with any tool set.- Files:
apps/api/src/audittrail/routes/ablation.py,apps/api/src/audittrail/causal.py,apps/api/src/audittrail/surrogate.py
- Files:
-
Fixed: Single reasoning node replaced with multiple nodes. The Sankey middle column showed only one "Tool Selector" node. Now generates multiple reasoning steps (e.g., "Identify computation need", "Resolve data source", "Plan comparison logic") based on tool names, with a keyword-to-label mapping and a dynamic fallback for unknown tool names.
- File:
apps/api/src/audittrail/causal.py
- File:
-
Fixed: Spider-web links replaced with targeted connections. Every prompt phrase was connecting to every reasoning node via the fallback
connected_reasoning = set(reasoning_nodes.keys()). Replaced with text-affinity scoring that matches phrase content against tool names. Each phrase connects to 1-3 most relevant reasoning nodes, with weight proportional to affinity strength.- File:
apps/api/src/audittrail/causal.py
- File:
-
Fixed: Link thickness normalization. Sankey link glow width was
Math.max(3, attr * 40)which produced uniform thin lines when all attribution values were small. Now normalizes against the maximum link value so the strongest link always fills the visual range.- File:
apps/web/components/sankey/sankey-viewer.tsx
- File:
-
Fixed: Hover tooltip on Sankey links. SVG container had
pointerEvents: "none"which blocked all mouse events on hit-area paths. Split into two SVG layers: Layer 1 (z-index 1) for visual paths withpointerEvents: "none", Layer 2 (z-index 10) for invisible hit-area paths withpointerEvents: "stroke". Tooltip now appears on hover showing source, target, attribution score, and confidence level.- File:
apps/web/components/sankey/sankey-viewer.tsx
- File:
Live Tracing
-
Added: Incremental span ingestion. Test agent now sends spans during execution using
send_running()(on tool/LLM start) andsend_complete()(on end), instead of batching all spans at the end. The trace shows as "Running" during execution and transitions to "Complete" when done.- File:
test-agent/agent.py(nowexamples/langgraph-agent/agent.py)
- File:
-
Added: WebSocket broadcast for HTTP span ingestion. The
POST /api/v1/ingest/spansendpoint now broadcastsspan_start/span_endevents via WebSocket after each span upsert. Previously only internal@traceabledecorator spans triggered broadcasts.- File:
apps/api/src/audittrail/routes/ingest.py
- File:
-
Added: Hierarchical live spans. The test agent creates intermediate grouping spans (
execute_toolschain,research_sub_agentagent) dynamically during execution, producing a multi-layered DAG instead of a flat tree.- File:
test-agent/agent.py(nowexamples/langgraph-agent/agent.py)
- File:
-
Fixed: Trace status stuck on "Running". The trace detail page did not subscribe to WebSocket events, so the status badge never updated after the trace completed. Added
useTraceWebSocketsubscription that invalidates["trace", id]and["trace-spans", id]queries on span events.- File:
apps/web/app/(dashboard)/traces/[id]/page.tsx
- File:
Security & User Isolation
-
Added:
user_idcolumn on Trace model. Traces are now owned by the user who created them. Each user only sees their own traces across all endpoints (traces, analytics, evaluations, ablation, spans). Column is nullable — existing traces with NULL user_id are invisible to all users (orphaned).- Files:
apps/api/src/audittrail/models.py,apps/api/src/audittrail/schemas.py
- Files:
-
Added: Safe schema migration in init_db(). On startup,
init_db()checks if theuser_idcolumn exists on thetracestable. If missing, runsALTER TABLE traces ADD COLUMN user_idwith FK constraint and index. Safe to run repeatedly.- File:
apps/api/src/audittrail/database.py
- File:
-
Added: Authentication on all trace-related endpoints. 35+ endpoints across 7 route files now require
Depends(get_current_user)and filter queries byTrace.user_id == user.id. Unauthenticated requests return 401.- Files:
routes/traces.py(9 endpoints),routes/analytics.py(7 endpoints),routes/evaluations.py(6 endpoints),routes/ablation.py(6 endpoints),routes/spans.py(2 endpoints)
- Files:
-
Added: Optional auth on ingest endpoints.
POST /ingest/spansandPOST /ingest/tracesuseget_optional_user— authenticated users get traces assigned to their user_id; unauthenticated agents create traces with user_id=NULL.- File:
apps/api/src/audittrail/routes/ingest.py
- File:
-
Added: Admin-only flush endpoint.
DELETE /api/v1/traces/flush-alldeletes ALL traces in the database. Requires admin role. For clearing dev/seed data.- File:
apps/api/src/audittrail/routes/traces.py
- File:
-
Added: Frontend 401 redirect. API client now detects 401 responses and redirects the browser to
/login. Prevents broken dashboard state when session expires.- File:
apps/web/lib/api-client.ts
- File:
-
Fixed: Demo mode isolation.
useCurrentUserhook no longer falls back to "Priya S." demo user on auth errors. Demo mode only activates when?demo=trueis in the URL (triggered by "View Demo" buttons on landing page). Real users see empty state if they have no traces.- Files:
apps/web/hooks/use-current-user.ts,apps/web/app/(dashboard)/layout.tsx,apps/web/app/page.tsx
- Files:
Documentation
-
Updated: Quickstart guide. Complete rewrite with zero-to-dashboard flow, agent integration guide (HTTP + Callback + Template), common pitfalls table, live tracing pattern, and corrected example paths.
- File:
docs/quickstart.md
- File:
-
Added: Example LangGraph agent. A complete working agent with 7 tools (web_search, calculator, get_current_time, summarize_findings, database_lookup, read_config_file, compare_values) that demonstrates live tracing, hierarchical span grouping, and correct tool_calls format.
- Files:
examples/langgraph-agent/agent.py,examples/langgraph-agent/requirements.txt
- Files:
-
Added: Goal-agnostic agent template. A minimal copy-paste-ready template (
template.py) with the AuditTrailIngestor class, placeholder tools, and clear TODO markers for customization.- File:
examples/langgraph-agent/template.py
- File:
-
Added: Examples README. Usage guide with architecture diagram, tool format documentation, and response envelope warning.
- File:
examples/langgraph-agent/README.md
- File:
-
Added: Changelog. This file documenting all session changes.
- File:
docs/changelog.md
- File: