diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md
index 7e8fc96..75117c9 100644
--- a/BUILD_REPORT.md
+++ b/BUILD_REPORT.md
@@ -2,198 +2,108 @@
## sprint objective
-Implement Sprint 6D by exposing deterministic user-scoped read-only trace review APIs for:
-
-- `GET /v0/traces`
-- `GET /v0/traces/{trace_id}`
-- `GET /v0/traces/{trace_id}/events`
-
-The sprint stayed limited to explain-why trace reads over existing persisted `traces` and `trace_events` data.
+Implement Sprint 6E by replacing the fixture-only `/traces` route with a live explain-why review surface that uses the shipped backend trace review APIs when API configuration is present, while preserving explicit fixture fallback only when live configuration is absent.
## completed work
-- introduced stable trace review contracts in `apps/api/src/alicebot_api/contracts.py`
- - `TraceReviewSummaryRecord`
- - `TraceReviewRecord`
- - `TraceReviewListSummary`
- - `TraceReviewListResponse`
- - `TraceReviewDetailResponse`
- - `TraceReviewEventRecord`
- - `TraceReviewEventListSummary`
- - `TraceReviewEventListResponse`
- - `TRACE_REVIEW_LIST_ORDER`
- - `TRACE_REVIEW_EVENT_LIST_ORDER`
-- added a narrow trace review module in `apps/api/src/alicebot_api/traces.py`
- - `list_trace_records()`
- - `get_trace_record()`
- - `list_trace_event_records()`
- - `TraceNotFoundError`
-- extended `apps/api/src/alicebot_api/store.py` with read-only trace review queries
- - `list_trace_reviews()`
- - `get_trace_review_optional()`
- - deterministic list SQL with trace-event counts
- - deterministic trace-event SQL ordering
-- added FastAPI endpoints in `apps/api/src/alicebot_api/main.py`
+- extended `apps/web/lib/api.ts` with typed trace review reads for:
- `GET /v0/traces`
- `GET /v0/traces/{trace_id}`
- `GET /v0/traces/{trace_id}/events`
-- added unit coverage in `tests/unit/test_traces.py` for
- - deterministic list ordering
- - stable detail shape
- - stable event-list shape
- - invisible-trace not-found behavior
- - endpoint translation and 404 mapping
-- added Postgres-backed integration coverage in `tests/integration/test_traces_api.py` for
- - deterministic trace list ordering
- - trace detail reads
- - ordered trace-event reads
- - cross-user isolation
- - invisible-trace 404 behavior
+- moved `/traces` fixture data into `apps/web/lib/fixtures.ts` and added `getFixtureTrace()` for explicit no-config fallback
+- replaced `apps/web/app/traces/page.tsx` with live route wiring that:
+ - uses live trace list/detail/event reads when API configuration is present
+ - stays fixture-backed when API configuration is absent
+ - shows an explicit API-unavailable state when live trace reads fail
+ - keeps partial live detail bounded when detail or event reads fail
+- added `apps/web/app/traces/loading.tsx` route-level loading UI
+- updated `apps/web/components/trace-list.tsx` to render:
+ - live trace summaries
+ - key metadata
+ - ordered event review
+ - empty state
+ - API-unavailable state
+ - bounded partial event-unavailable state
+- added narrow frontend coverage in:
+ - `apps/web/lib/api.test.ts`
+ - `apps/web/components/trace-list.test.tsx`
+ - route-level `/traces` branching coverage for fixture-backed, live-unavailable, and partial live-event states
## incomplete work
-- none inside the sprint’s scoped backend deliverables
-- no UI migration from fixture-backed `/traces`
-- no trace creation or mutation changes
-- no filtering, search, or expanded explainability surface beyond the three read endpoints
+- none inside the sprint’s scoped `/traces` UI deliverables
+- intentionally not added:
+ - trace filtering
+ - trace search
+ - trace pagination
+ - trace mutation UI
+ - backend changes
## files changed
-- `apps/api/src/alicebot_api/contracts.py`
-- `apps/api/src/alicebot_api/main.py`
-- `apps/api/src/alicebot_api/store.py`
-- `apps/api/src/alicebot_api/traces.py`
-- `tests/unit/test_traces.py`
-- `tests/integration/test_traces_api.py`
+- `apps/web/app/traces/page.tsx`
+- `apps/web/app/traces/loading.tsx`
+- `apps/web/components/trace-list.tsx`
+- `apps/web/lib/api.ts`
+- `apps/web/lib/fixtures.ts`
+- `apps/web/lib/api.test.ts`
+- `apps/web/components/trace-list.test.tsx`
- `BUILD_REPORT.md`
-## exact ordering rules
+## route backing mode
+
+- `/traces` is live-API-backed when API configuration is present
+- `/traces` is fixture-backed when API configuration is absent
+- `/traces` shows an explicit unavailable state when live API configuration is present but trace reads fail
+- the route is not mixed in the steady-state implementation
-- trace list reads use `created_at DESC, id DESC`
-- trace-event reads use `sequence_no ASC, id ASC`
+## backend endpoints consumed
+
+- `GET /v0/traces`
+- `GET /v0/traces/{trace_id}`
+- `GET /v0/traces/{trace_id}/events`
## tests run
-- `./.venv/bin/python -m pytest tests/unit/test_traces.py`
+- `npm run lint`
- PASS
- - `5` tests passed
-- `./.venv/bin/python -m pytest tests/integration/test_traces_api.py`
- - initial sandboxed run could not reach local Postgres on `localhost:5432`
- - rerun as part of the full integration suite below passed
-- `./.venv/bin/python -m pytest tests/unit`
+- `npm test`
- PASS
- - `451` tests passed
-- `./.venv/bin/python -m pytest tests/integration`
+ - `3` test files passed
+ - `13` tests passed
+- `npm run build`
- PASS
- - `143` tests passed
-
-## unit and integration test results
-
-- unit result: PASS
- - trace review module coverage and endpoint translation coverage passed
-- integration result: PASS
- - live Postgres-backed trace review list/detail/event and isolation coverage passed
-
-## example trace list response
-
-```json
-{
- "items": [
- {
- "id": "00000000-0000-4000-8000-000000000002",
- "thread_id": "11111111-1111-4111-8111-111111111111",
- "kind": "tool.proxy.execute",
- "compiler_version": "response_generation_v0",
- "status": "completed",
- "created_at": "2026-03-17T09:00:00+00:00",
- "trace_event_count": 2
- },
- {
- "id": "00000000-0000-4000-8000-000000000001",
- "thread_id": "11111111-1111-4111-8111-111111111111",
- "kind": "context.compile",
- "compiler_version": "continuity_v0",
- "status": "completed",
- "created_at": "2026-03-17T09:00:00+00:00",
- "trace_event_count": 1
- }
- ],
- "summary": {
- "total_count": 2,
- "order": ["created_at_desc", "id_desc"]
- }
-}
-```
-
-## example trace detail response
-
-```json
-{
- "trace": {
- "id": "00000000-0000-4000-8000-000000000002",
- "thread_id": "11111111-1111-4111-8111-111111111111",
- "kind": "tool.proxy.execute",
- "compiler_version": "response_generation_v0",
- "status": "completed",
- "limits": {
- "max_sessions": 1,
- "max_events": 2
- },
- "created_at": "2026-03-17T09:00:00+00:00",
- "trace_event_count": 2
- }
-}
-```
-
-## example trace-event list response
-
-```json
-{
- "items": [
- {
- "id": "10000000-0000-4000-8000-000000000002",
- "trace_id": "00000000-0000-4000-8000-000000000002",
- "sequence_no": 1,
- "kind": "tool.proxy.execute.request",
- "payload": {
- "approval_id": "approval-2"
- },
- "created_at": "2026-03-17T09:00:00+00:00"
- },
- {
- "id": "10000000-0000-4000-8000-000000000001",
- "trace_id": "00000000-0000-4000-8000-000000000002",
- "sequence_no": 2,
- "kind": "tool.proxy.execute.summary",
- "payload": {
- "approval_id": "approval-2"
- },
- "created_at": "2026-03-17T09:00:00+00:00"
- }
- ],
- "summary": {
- "trace_id": "00000000-0000-4000-8000-000000000002",
- "total_count": 2,
- "order": ["sequence_no_asc", "id_asc"]
- }
-}
-```
+
+## exact commands run
+
+- `cd /Users/samirusani/Desktop/Codex/AliceBot/apps/web && npm run lint`
+- `cd /Users/samirusani/Desktop/Codex/AliceBot/apps/web && npm test`
+- `cd /Users/samirusani/Desktop/Codex/AliceBot/apps/web && npm run build`
+
+## lint, test, and build results
+
+- lint result: PASS
+- test result: PASS
+- build result: PASS
+
+## desktop and mobile visual verification notes
+
+- no browser-based visual QA pass was executed in this turn
+- desktop note: code inspection indicates the existing split review layout remains in place for `/traces`, with summary, metadata, and ordered events kept in bounded cards
+- mobile note: code inspection indicates the trace route still collapses to one column below the shared shell breakpoints in `apps/web/app/globals.css`
## blockers/issues
-- no remaining product-scope blockers
-- one execution-time environment issue occurred during verification
- - sandboxed integration setup could not connect to local Postgres on `localhost:5432`
- - rerunning the required integration suite with local database access resolved verification
+- no implementation blockers remain inside sprint scope
+- no backend contract changes were required
## recommended next step
-Hook the existing `/traces` web surface off these live endpoints in a separate sprint, while preserving the same narrow persisted-data-only contract.
+Run a browser-based QA pass against a live configured backend with real trace records to validate the wording and density of the generated live summaries and event facts.
## intentionally deferred after this sprint
-- any UI changes
-- any trace mutation endpoints
-- any new trace production behavior
-- any connector, Gmail, Calendar, approval-flow, or execution-scope expansion
-- any search, filtering, pagination, or explainability enrichment beyond persisted trace and trace-event reads
+- any Gmail, Calendar, auth, runner, or broader task workflow scope
+- any redesign outside the existing `/traces` shell
+- any trace enrichment beyond the shipped list/detail/event endpoints
+- any search, filtering, pagination, or mutation controls
diff --git a/REVIEW_REPORT.md b/REVIEW_REPORT.md
index c20c146..a88af32 100644
--- a/REVIEW_REPORT.md
+++ b/REVIEW_REPORT.md
@@ -6,49 +6,48 @@ PASS
## criteria met
-- `GET /v0/traces`, `GET /v0/traces/{trace_id}`, and `GET /v0/traces/{trace_id}/events` are implemented in `apps/api/src/alicebot_api/main.py`.
-- The sprint stayed limited to read-only trace review over existing persisted `traces` and `trace_events` data.
-- Stable trace review contracts were added in `apps/api/src/alicebot_api/contracts.py`.
-- The review seam in `apps/api/src/alicebot_api/traces.py` returns deterministic list, detail, and event payloads.
-- Deterministic ordering is explicit and test-backed:
- - trace list: `created_at DESC, id DESC`
- - trace events: `sequence_no ASC, id ASC`
-- User isolation is preserved through user-scoped connections plus existing row-level security behavior, and invisible traces return `404`.
-- Unit coverage was added for ordering, response shape, endpoint mapping, and invisible-trace handling.
-- Integration coverage was added for list/detail/event reads, cross-user isolation, and invisible-trace `404` behavior.
-- Acceptance verification passed:
- - `./.venv/bin/python -m pytest tests/unit` -> `451 passed`
- - `./.venv/bin/python -m pytest tests/integration` -> `143 passed`
+- The sprint stayed a UI sprint and did not widen backend scope.
+- `/traces` now uses the shipped trace review APIs when API configuration is present via the shared helper reads in [apps/web/lib/api.ts](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/lib/api.ts) and the route wiring in [apps/web/app/traces/page.tsx](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/app/traces/page.tsx).
+- `/traces` no longer depends exclusively on local fixture data. When API configuration is absent, the route falls back explicitly to fixture data from [apps/web/lib/fixtures.ts](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/lib/fixtures.ts).
+- Loading, empty, unavailable, and bounded partial-detail/event states are implemented across [apps/web/app/traces/loading.tsx](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/app/traces/loading.tsx), [apps/web/app/traces/page.tsx](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/app/traces/page.tsx), and [apps/web/components/trace-list.tsx](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/components/trace-list.tsx).
+- The trace UI keeps the intended bounded visual hierarchy: summary first, key metadata second, ordered events third.
+- The implementation stays within the Sprint 6E in-scope files and does not add Gmail, Calendar, auth, runner, filtering, search, pagination, mutation, or backend changes.
+- `BUILD_REPORT.md` is aligned to Sprint 6E and documents the route mode, shipped endpoints, exact commands, verification results, visual notes, and deferred scope.
+- Frontend coverage was added in:
+ - [apps/web/lib/api.test.ts](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/lib/api.test.ts)
+ - [apps/web/components/trace-list.test.tsx](/Users/samirusani/Desktop/Codex/AliceBot/apps/web/components/trace-list.test.tsx)
+- Verification passed in `apps/web`:
+ - `npm run lint`
+ - `npm test`
+ - `npm run build`
+ - current totals: `3` test files, `13` tests
+- `next build` did not leave tracked churn in `apps/web/tsconfig.json` or `apps/web/next-env.d.ts`.
## criteria missed
-- none
+- None.
## quality issues
-- none blocking
-- No sloppy scope expansion was found. The diff is confined to the intended API/store/contracts/test surface plus `BUILD_REPORT.md`.
+- No blocking quality issues found in the current Sprint 6E implementation.
## regression risks
-- Low risk overall. The new seam is narrow and read-only.
-- The main ongoing dependency is correct use of `user_connection()` so row-level security remains active for trace visibility. Current unit and integration coverage exercises that path.
+- Residual product risk is limited to real-data wording and density because the visual verification notes are code-inspection notes rather than a browser QA pass. This does not block Sprint 6E acceptance but is still worth validating against a live configured backend.
## docs issues
-- none blocking
-- `BUILD_REPORT.md` matches the sprint packet requirements, including contracts, ordering rules, commands run, example responses, and deferred scope.
+- No blocking docs issues remain for Sprint 6E.
## should anything be added to RULES.md?
-- no
+- No.
## should anything update ARCHITECTURE.md?
-- no required update for sprint acceptance
-- Optional future update: document the new read-only trace review API surface when the `/traces` UI switches from fixtures to live backend reads.
+- No.
## recommended next action
-- Accept the sprint.
-- In a follow-up sprint, replace the fixture-backed `/traces` UI with these live endpoints without widening the backend contract.
+- Sprint 6E can be considered review-passed.
+- Next follow-up should be a browser-based QA pass against a live configured backend with real trace records to validate the operator-facing wording and event density.
diff --git a/apps/web/app/traces/loading.tsx b/apps/web/app/traces/loading.tsx
new file mode 100644
index 0000000..ebafbdf
--- /dev/null
+++ b/apps/web/app/traces/loading.tsx
@@ -0,0 +1,51 @@
+import { PageHeader } from "../../components/page-header";
+import { SectionCard } from "../../components/section-card";
+import { StatusBadge } from "../../components/status-badge";
+
+export default function Loading() {
+ return (
+
+
+ Loading route state
+
+ }
+ />
+
+
+
+ );
+}
diff --git a/apps/web/app/traces/page.tsx b/apps/web/app/traces/page.tsx
index 5bf40fb..e2eee77 100644
--- a/apps/web/app/traces/page.tsx
+++ b/apps/web/app/traces/page.tsx
@@ -1,134 +1,162 @@
import { PageHeader } from "../../components/page-header";
import { SectionCard } from "../../components/section-card";
-import { TraceList, type TraceItem } from "../../components/trace-list";
-
-const traceFixtures: TraceItem[] = [
- {
- id: "trace-ctx-401",
- kind: "context_compile",
- status: "completed",
- title: "Context compile for magnesium reorder guidance",
- summary: "Compiled prior task state, admitted memories, and recent thread continuity before assistant response assembly.",
- eventCount: 9,
- createdAt: "2026-03-17T08:45:00Z",
- source: "Context compiler",
- scope: "thread-magnesium",
- related: {
- threadId: "thread-magnesium",
- taskId: "task-201",
- },
- evidence: [
- "Memory evidence admitted for supplement preference and merchant history.",
- "Recent approval state included as part of the continuity pack.",
- "Task-step lineage referenced before response generation.",
- ],
- events: [
- {
- id: "event-1",
- kind: "compiler.scope",
- title: "Scope resolved",
- detail: "Single-user thread scope and compile limits were established for the request.",
- },
- {
- id: "event-2",
- kind: "memory.retrieve",
- title: "Memory evidence attached",
- detail: "Preference and purchase-history memories were ranked into the response context pack.",
- },
- {
- id: "event-3",
- kind: "task.retrieve",
- title: "Task lifecycle linked",
- detail: "Open task and step state were included so the answer could acknowledge the approval dependency.",
- },
- ],
- },
- {
- id: "trace-approval-101",
- kind: "approval_request",
- status: "requires_review",
- title: "Approval request for supplement purchase",
- summary: "Routing required user approval before the merchant proxy could execute the purchase request.",
- eventCount: 6,
- createdAt: "2026-03-17T06:50:00Z",
- source: "Approval workflow",
- scope: "supplements",
- related: {
- threadId: "thread-magnesium",
- taskId: "task-201",
- approvalId: "approval-101",
- },
- evidence: [
- "Policy rule marked purchase actions as approval-gated.",
- "Tool metadata matched the requested action and scope.",
- "Task-step trace link points back to the original governed request.",
- ],
- events: [
- {
- id: "event-4",
- kind: "tool.route",
- title: "Routing completed",
- detail: "The merchant proxy was selected as the governing tool for the request.",
- },
- {
- id: "event-5",
- kind: "approval.state",
- title: "Approval opened",
- detail: "Approval record persisted with pending resolution state and task-step linkage.",
- },
- {
- id: "event-6",
- kind: "task.lifecycle",
- title: "Task updated",
- detail: "Task lifecycle moved into a pending approval state while retaining request provenance.",
- },
- ],
- },
- {
- id: "trace-exec-311",
- kind: "proxy_execution",
- status: "completed",
- title: "Governed execution for vitamin reorder",
- summary: "Approved supplement purchase request executed through the proxy handler with task and trace linkage preserved.",
- eventCount: 7,
- createdAt: "2026-03-16T14:24:00Z",
- source: "Proxy execution",
- scope: "supplements",
+import { TraceList, type TraceEventItem, type TraceItem } from "../../components/trace-list";
+import {
+ getApiConfig,
+ getTraceDetail,
+ getTraceEvents,
+ hasLiveApiConfig,
+ listTraces,
+ pageModeLabel,
+ type TraceReviewEventItem,
+ type TraceReviewItem,
+ type TraceReviewSummaryItem,
+} from "../../lib/api";
+import { getFixtureTrace, traceFixtures } from "../../lib/fixtures";
+
+type SearchParams = Promise>;
+
+function formatKind(kind: string) {
+ return kind
+ .split(/[._]/)
+ .filter(Boolean)
+ .map((part) => part[0]?.toUpperCase() + part.slice(1))
+ .join(" ");
+}
+
+function formatStatus(status: string) {
+ return status.replaceAll("_", " ");
+}
+
+function shortId(value: string) {
+ return value.length > 12 ? `${value.slice(0, 8)}...${value.slice(-4)}` : value;
+}
+
+function stringifyValue(value: unknown): string {
+ if (typeof value === "string") {
+ return value;
+ }
+
+ if (typeof value === "number" || typeof value === "boolean") {
+ return String(value);
+ }
+
+ if (value === null) {
+ return "null";
+ }
+
+ if (Array.isArray(value)) {
+ return value.map((item) => stringifyValue(item)).join(", ");
+ }
+
+ if (typeof value === "object") {
+ return JSON.stringify(value);
+ }
+
+ return "unknown";
+}
+
+function buildTraceSummary(trace: TraceReviewSummaryItem | TraceReviewItem) {
+ const eventLabel = trace.trace_event_count === 1 ? "ordered event" : "ordered events";
+ return `${formatKind(trace.kind)} recorded ${trace.trace_event_count} ${eventLabel} for thread ${shortId(trace.thread_id)} and ended in ${formatStatus(trace.status)} status.`;
+}
+
+function buildBaseTraceItem(trace: TraceReviewSummaryItem): TraceItem {
+ return {
+ id: trace.id,
+ kind: trace.kind,
+ status: trace.status,
+ title: `${formatKind(trace.kind)} review`,
+ summary: buildTraceSummary(trace),
+ eventCount: trace.trace_event_count,
+ createdAt: trace.created_at,
+ source: trace.compiler_version,
+ scope: `Thread ${shortId(trace.thread_id)}`,
related: {
- threadId: "thread-vitamin-d",
- taskId: "task-182",
- approvalId: "approval-100",
- executionId: "execution-311",
+ threadId: trace.thread_id,
+ compilerVersion: trace.compiler_version,
},
- evidence: [
- "Execution occurred only after approval resolution.",
- "Handler output and trace references stayed attached to the governed action record.",
- "Task and task-step lifecycle traces were appended alongside execution status.",
- ],
- events: [
- {
- id: "event-7",
- kind: "approval.check",
- title: "Approval validated",
- detail: "Execution preflight confirmed the approval was in an executable state.",
- },
- {
- id: "event-8",
- kind: "budget.check",
- title: "Budget check passed",
- detail: "Execution budget constraints did not block the governed action.",
- },
- {
- id: "event-9",
- kind: "execution.result",
- title: "Handler completed",
- detail: "Proxy output was recorded for the approved supplement reorder with a linked execution trace and task-step status update.",
- },
+ metadata: [
+ `Trace: ${trace.id}`,
+ `Thread: ${trace.thread_id}`,
+ `Compiler: ${trace.compiler_version}`,
+ `Status: ${formatStatus(trace.status)}`,
],
+ evidence: [],
+ events: [],
+ detailSource: "live",
+ eventSource: "live",
+ };
+}
+
+function buildEventFacts(event: TraceReviewEventItem) {
+ const payload = event.payload;
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
+ return [`Sequence ${event.sequence_no}`, `Payload: ${stringifyValue(payload)}`];
+ }
+
+ const entries = Object.entries(payload as Record).slice(0, 4);
+ return [
+ `Sequence ${event.sequence_no}`,
+ ...entries.map(([key, value]) => `${key}: ${stringifyValue(value)}`),
+ ];
+}
+
+function buildEventDetail(event: TraceReviewEventItem) {
+ const payload = event.payload;
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
+ return `This event captured payload value ${stringifyValue(payload)}.`;
+ }
+
+ const keys = Object.keys(payload as Record);
+ if (keys.length === 0) {
+ return "This event completed without additional payload fields.";
+ }
+
+ return `This event captured ${keys.length} payload field${keys.length === 1 ? "" : "s"} for operator review.`;
+}
+
+function buildEventTitle(event: TraceReviewEventItem) {
+ return `${formatKind(event.kind)} event`;
+}
+
+function buildLiveTraceItem(
+ trace: TraceReviewItem,
+ events: TraceReviewEventItem[],
+ options?: {
+ detailUnavailable?: boolean;
+ eventsUnavailable?: boolean;
},
-];
+): TraceItem {
+ const metadata = [
+ `Trace: ${trace.id}`,
+ `Thread: ${trace.thread_id}`,
+ `Compiler: ${trace.compiler_version}`,
+ `Status: ${formatStatus(trace.status)}`,
+ ...Object.entries(trace.limits).map(([key, value]) => `Limit ${key}: ${stringifyValue(value)}`),
+ ];
-type SearchParams = Promise>;
+ const evidence = events.length
+ ? [
+ `${events.length} ordered event${events.length === 1 ? "" : "s"} loaded from the shipped trace review API.`,
+ ]
+ : ["No ordered events were returned for this trace."];
+
+ return {
+ ...buildBaseTraceItem(trace),
+ metadata,
+ evidence,
+ events: events.map((event) => ({
+ id: event.id,
+ kind: event.kind,
+ title: buildEventTitle(event),
+ detail: buildEventDetail(event),
+ facts: buildEventFacts(event),
+ })),
+ detailUnavailable: options?.detailUnavailable,
+ eventsUnavailable: options?.eventsUnavailable,
+ };
+}
export default async function TracesPage({
searchParams,
@@ -139,33 +167,107 @@ export default async function TracesPage({
string,
string | string[] | undefined
>;
- const selectedId = typeof params.trace === "string" ? params.trace : undefined;
+ const requestedId = typeof params.trace === "string" ? params.trace : undefined;
+ const apiConfig = getApiConfig();
+ const liveModeReady = hasLiveApiConfig(apiConfig);
+
+ let traces = traceFixtures;
+ let apiUnavailable = false;
+
+ if (liveModeReady) {
+ try {
+ const payload = await listTraces(apiConfig.apiBaseUrl, apiConfig.userId);
+ const selectedSummary = payload.items.find((item) => item.id === requestedId) ?? payload.items[0] ?? null;
+ const mapped = payload.items.map((item) => buildBaseTraceItem(item));
+
+ if (selectedSummary) {
+ const selectedIndex = mapped.findIndex((item) => item.id === selectedSummary.id);
+ let selectedTrace = mapped[selectedIndex];
+
+ try {
+ const detailPayload = await getTraceDetail(
+ apiConfig.apiBaseUrl,
+ selectedSummary.id,
+ apiConfig.userId,
+ );
+
+ try {
+ const eventPayload = await getTraceEvents(
+ apiConfig.apiBaseUrl,
+ selectedSummary.id,
+ apiConfig.userId,
+ );
+
+ selectedTrace = buildLiveTraceItem(detailPayload.trace, eventPayload.items);
+ } catch {
+ selectedTrace = buildLiveTraceItem(detailPayload.trace, [], {
+ eventsUnavailable: true,
+ });
+ }
+ } catch {
+ selectedTrace = {
+ ...selectedTrace,
+ metadata: [
+ `Trace: ${selectedSummary.id}`,
+ `Thread: ${selectedSummary.thread_id}`,
+ `Compiler: ${selectedSummary.compiler_version}`,
+ `Status: ${formatStatus(selectedSummary.status)}`,
+ ],
+ evidence: [
+ "The selected summary came from the live trace list, but full trace detail could not be read.",
+ ],
+ detailUnavailable: true,
+ eventsUnavailable: true,
+ };
+ }
+
+ if (selectedIndex >= 0) {
+ mapped[selectedIndex] = selectedTrace;
+ }
+ }
+
+ traces = mapped;
+ } catch {
+ traces = [];
+ apiUnavailable = true;
+ }
+ } else {
+ const selectedFixture = requestedId ? getFixtureTrace(requestedId) : null;
+ if (selectedFixture) {
+ traces = [selectedFixture, ...traceFixtures.filter((item) => item.id !== selectedFixture.id)];
+ }
+ }
+
+ const selectedId = requestedId ?? traces[0]?.id;
+ const pageMode = liveModeReady ? "live" : "fixture";
return (
- Fixture-backed detail view
- Existing backend concepts only
+ {pageModeLabel(pageMode)}
+
+ {apiUnavailable ? "Trace API unavailable" : `${traces.length} entries`}
+
}
/>
-
+
- - Which evidence types contributed to the outcome and whether they were appropriate.
- - How the lifecycle moved from request to approval or execution without losing provenance.
- - Whether the current trace surface needs deeper live-event wiring in a future sprint.
+ - Whether the summary matches the trace kind, status, and ordered event count returned by the backend.
+ - Whether key metadata keeps thread, compiler, and limit context visible without turning into a debugger dump.
+ - Whether the ordered events explain the outcome clearly enough without requiring broader trace filtering or mutation scope.
diff --git a/apps/web/components/trace-list.test.tsx b/apps/web/components/trace-list.test.tsx
new file mode 100644
index 0000000..1807ae4
--- /dev/null
+++ b/apps/web/components/trace-list.test.tsx
@@ -0,0 +1,239 @@
+import React from "react";
+import { cleanup, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import TracesPage from "../app/traces/page";
+import { TraceList, type TraceItem } from "./trace-list";
+
+const {
+ getApiConfigMock,
+ hasLiveApiConfigMock,
+ listTracesMock,
+ getTraceDetailMock,
+ getTraceEventsMock,
+} = vi.hoisted(() => ({
+ getApiConfigMock: vi.fn(),
+ hasLiveApiConfigMock: vi.fn(),
+ listTracesMock: vi.fn(),
+ getTraceDetailMock: vi.fn(),
+ getTraceEventsMock: vi.fn(),
+}));
+
+vi.mock("next/link", () => ({
+ default: ({
+ href,
+ children,
+ className,
+ }: {
+ href: string;
+ children: React.ReactNode;
+ className?: string;
+ }) => (
+
+ {children}
+
+ ),
+}));
+
+vi.mock("../lib/api", async () => {
+ const actual = await vi.importActual("../lib/api");
+ return {
+ ...actual,
+ getApiConfig: getApiConfigMock,
+ hasLiveApiConfig: hasLiveApiConfigMock,
+ listTraces: listTracesMock,
+ getTraceDetail: getTraceDetailMock,
+ getTraceEvents: getTraceEventsMock,
+ };
+});
+
+const liveTrace: TraceItem = {
+ id: "trace-1",
+ kind: "context.compile",
+ status: "completed",
+ title: "Context Compile review",
+ summary: "Context Compile recorded 2 ordered events for thread thread-1 and ended in completed status.",
+ eventCount: 2,
+ createdAt: "2026-03-17T00:00:00Z",
+ source: "continuity_v0",
+ scope: "Thread thread-1",
+ related: {
+ threadId: "thread-1",
+ compilerVersion: "continuity_v0",
+ },
+ metadata: ["Trace: trace-1", "Thread: thread-1", "Compiler: continuity_v0"],
+ evidence: ["2 ordered events loaded from the shipped trace review API."],
+ events: [
+ {
+ id: "event-1",
+ kind: "context.summary",
+ title: "Context Summary event",
+ detail: "This event captured 1 payload field for operator review.",
+ facts: ["Sequence 1", "thread_id: thread-1"],
+ },
+ ],
+ detailSource: "live",
+ eventSource: "live",
+};
+
+describe("TraceList", () => {
+ beforeEach(() => {
+ getApiConfigMock.mockReset();
+ hasLiveApiConfigMock.mockReset();
+ listTracesMock.mockReset();
+ getTraceDetailMock.mockReset();
+ getTraceEventsMock.mockReset();
+ });
+
+ afterEach(() => {
+ cleanup();
+ });
+
+ it("renders an explicit unavailable state when the configured trace API cannot be reached", () => {
+ render();
+
+ expect(screen.getByText("Trace API unavailable")).toBeInTheDocument();
+ expect(screen.getByText("No live trace detail")).toBeInTheDocument();
+ });
+
+ it("renders an empty split state when no traces are available", () => {
+ render();
+
+ expect(screen.getByText("Trace review is empty")).toBeInTheDocument();
+ expect(screen.getByText("Explain-why detail is idle")).toBeInTheDocument();
+ });
+
+ it("keeps the selected trace bounded when ordered events are unavailable", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Key metadata")).toBeInTheDocument();
+ expect(screen.getByText("Ordered events unavailable")).toBeInTheDocument();
+ expect(screen.getByText("Detail: Live trace detail")).toBeInTheDocument();
+ });
+
+ it("renders ordered event review for a selected trace", () => {
+ render();
+
+ expect(screen.getAllByText("Context Compile review")).toHaveLength(2);
+ expect(screen.getByText("Context Summary event")).toBeInTheDocument();
+ expect(screen.getByText("Sequence 1")).toBeInTheDocument();
+ });
+});
+
+describe("TracesPage", () => {
+ beforeEach(() => {
+ getApiConfigMock.mockReset();
+ hasLiveApiConfigMock.mockReset();
+ listTracesMock.mockReset();
+ getTraceDetailMock.mockReset();
+ getTraceEventsMock.mockReset();
+ });
+
+ afterEach(() => {
+ cleanup();
+ });
+
+ it("stays fixture-backed when live API configuration is absent", async () => {
+ getApiConfigMock.mockReturnValue({
+ apiBaseUrl: "",
+ userId: "",
+ defaultThreadId: "",
+ defaultToolId: "",
+ });
+ hasLiveApiConfigMock.mockReturnValue(false);
+
+ render(
+ await TracesPage({
+ searchParams: Promise.resolve({
+ trace: "trace-approval-101",
+ }),
+ }),
+ );
+
+ expect(screen.getByText("Fixture-backed")).toBeInTheDocument();
+ expect(screen.getAllByText("Approval request review").length).toBeGreaterThan(0);
+ expect(listTracesMock).not.toHaveBeenCalled();
+ });
+
+ it("shows an explicit unavailable state when the live trace list cannot be loaded", async () => {
+ getApiConfigMock.mockReturnValue({
+ apiBaseUrl: "https://api.example.com",
+ userId: "user-1",
+ defaultThreadId: "",
+ defaultToolId: "",
+ });
+ hasLiveApiConfigMock.mockReturnValue(true);
+ listTracesMock.mockRejectedValue(new Error("trace list failed"));
+
+ render(await TracesPage({ searchParams: Promise.resolve({}) }));
+
+ expect(screen.getAllByText("Trace API unavailable")).toHaveLength(2);
+ expect(screen.getByText("Explainability review is unavailable")).toBeInTheDocument();
+ });
+
+ it("keeps the live route bounded when detail loads but ordered events fail", async () => {
+ getApiConfigMock.mockReturnValue({
+ apiBaseUrl: "https://api.example.com",
+ userId: "user-1",
+ defaultThreadId: "",
+ defaultToolId: "",
+ });
+ hasLiveApiConfigMock.mockReturnValue(true);
+ listTracesMock.mockResolvedValue({
+ items: [
+ {
+ id: "trace-1",
+ thread_id: "thread-1",
+ kind: "context.compile",
+ compiler_version: "continuity_v0",
+ status: "completed",
+ created_at: "2026-03-17T00:00:00Z",
+ trace_event_count: 2,
+ },
+ ],
+ summary: {
+ total_count: 1,
+ order: ["created_at_desc", "id_desc"],
+ },
+ });
+ getTraceDetailMock.mockResolvedValue({
+ trace: {
+ id: "trace-1",
+ thread_id: "thread-1",
+ kind: "context.compile",
+ compiler_version: "continuity_v0",
+ status: "completed",
+ created_at: "2026-03-17T00:00:00Z",
+ trace_event_count: 2,
+ limits: {
+ max_sessions: 3,
+ max_events: 8,
+ },
+ },
+ });
+ getTraceEventsMock.mockRejectedValue(new Error("event read failed"));
+
+ render(
+ await TracesPage({
+ searchParams: Promise.resolve({
+ trace: "trace-1",
+ }),
+ }),
+ );
+
+ expect(screen.getByText("Live API")).toBeInTheDocument();
+ expect(screen.getByText("Ordered events unavailable")).toBeInTheDocument();
+ expect(screen.getByText("Limit max_events: 8")).toBeInTheDocument();
+ });
+});
diff --git a/apps/web/components/trace-list.tsx b/apps/web/components/trace-list.tsx
index 223f14e..e2362f5 100644
--- a/apps/web/components/trace-list.tsx
+++ b/apps/web/components/trace-list.tsx
@@ -1,9 +1,18 @@
import Link from "next/link";
+import type { ApiSource } from "../lib/api";
import { EmptyState } from "./empty-state";
import { SectionCard } from "./section-card";
import { StatusBadge } from "./status-badge";
+export type TraceEventItem = {
+ id: string;
+ kind: string;
+ title: string;
+ detail: string;
+ facts?: string[];
+};
+
export type TraceItem = {
id: string;
kind: string;
@@ -19,14 +28,15 @@ export type TraceItem = {
taskId?: string;
approvalId?: string;
executionId?: string;
+ compilerVersion?: string;
};
+ metadata: string[];
evidence: string[];
- events: Array<{
- id: string;
- kind: string;
- title: string;
- detail: string;
- }>;
+ events: TraceEventItem[];
+ detailSource: ApiSource;
+ eventSource: ApiSource;
+ detailUnavailable?: boolean;
+ eventsUnavailable?: boolean;
};
function formatDate(value: string) {
@@ -41,22 +51,65 @@ function formatDate(value: string) {
export function TraceList({
traces,
selectedId,
+ apiUnavailable = false,
}: {
traces: TraceItem[];
selectedId?: string;
+ apiUnavailable?: boolean;
}) {
+ if (apiUnavailable) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+
if (traces.length === 0) {
return (
-
-
-
+
+
+
+
+
+
+
+
+
);
}
@@ -67,7 +120,7 @@ export function TraceList({
@@ -89,7 +142,7 @@ export function TraceList({
{trace.summary}
- {trace.kind.replace(/_/g, " ")}
+ {trace.kind.replaceAll(".", " ")}
{trace.eventCount} events
@@ -101,13 +154,17 @@ export function TraceList({
- {selected.kind.replace(/_/g, " ")} · {selected.eventCount} events
+ {selected.kind.replaceAll(".", " ")} · {selected.eventCount} events
@@ -116,6 +173,12 @@ export function TraceList({
Source: {selected.source}
Scope: {selected.scope}
+
+ Detail: {selected.detailSource === "live" ? "Live trace detail" : "Fixture trace detail"}
+
+
+ Events: {selected.eventSource === "live" ? "Live event review" : "Fixture event review"}
+
{selected.related.threadId ? (
Thread: {selected.related.threadId}
) : null}
@@ -128,13 +191,16 @@ export function TraceList({
{selected.related.executionId ? (
Execution: {selected.related.executionId}
) : null}
+ {selected.related.compilerVersion ? (
+ Compiler: {selected.related.compilerVersion}
+ ) : null}
-
Evidence in view
+
Key metadata
- {selected.evidence.map((item) => (
+ {selected.metadata.map((item) => (
{item}
@@ -142,21 +208,55 @@ export function TraceList({
+ {selected.evidence.length > 0 ? (
+
+
Review notes
+
+ {selected.evidence.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ ) : null}
+
-
Key events
-
- {selected.events.map((event) => (
- -
-
-
-
{event.kind}
-
{event.title}
+
Ordered events
+ {selected.eventsUnavailable ? (
+
+ ) : selected.events.length === 0 ? (
+
+ ) : (
+
+ {selected.events.map((event) => (
+ -
+
+
+ {event.kind}
+
{event.title}
+
-
-
{event.detail}
-
- ))}
-
+
{event.detail}
+ {event.facts?.length ? (
+
+ {event.facts.map((fact) => (
+
+ {fact}
+
+ ))}
+
+ ) : null}
+
+ ))}
+
+ )}
diff --git a/apps/web/lib/api.test.ts b/apps/web/lib/api.test.ts
index c6a5e05..e65d95d 100644
--- a/apps/web/lib/api.test.ts
+++ b/apps/web/lib/api.test.ts
@@ -3,6 +3,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
ApiError,
combinePageModes,
+ getTraceDetail,
+ getTraceEvents,
+ listTraces,
pageModeLabel,
resolveApproval,
submitApprovalRequest,
@@ -154,4 +157,99 @@ describe("api helpers", () => {
}),
);
});
+
+ it("reads the shipped trace review endpoints with user-scoped query params", async () => {
+ fetchMock.mockResolvedValueOnce(
+ new Response(
+ JSON.stringify({
+ items: [
+ {
+ id: "trace-1",
+ thread_id: "thread-1",
+ kind: "context.compile",
+ compiler_version: "continuity_v0",
+ status: "completed",
+ created_at: "2026-03-17T00:00:00Z",
+ trace_event_count: 2,
+ },
+ ],
+ summary: {
+ total_count: 1,
+ order: ["created_at_desc", "id_desc"],
+ },
+ }),
+ { status: 200, headers: { "Content-Type": "application/json" } },
+ ),
+ );
+ fetchMock.mockResolvedValueOnce(
+ new Response(
+ JSON.stringify({
+ trace: {
+ id: "trace-1",
+ thread_id: "thread-1",
+ kind: "context.compile",
+ compiler_version: "continuity_v0",
+ status: "completed",
+ created_at: "2026-03-17T00:00:00Z",
+ trace_event_count: 2,
+ limits: {
+ max_sessions: 3,
+ max_events: 8,
+ },
+ },
+ }),
+ { status: 200, headers: { "Content-Type": "application/json" } },
+ ),
+ );
+ fetchMock.mockResolvedValueOnce(
+ new Response(
+ JSON.stringify({
+ items: [
+ {
+ id: "event-1",
+ trace_id: "trace-1",
+ sequence_no: 1,
+ kind: "context.summary",
+ payload: {
+ thread_id: "thread-1",
+ },
+ created_at: "2026-03-17T00:00:01Z",
+ },
+ ],
+ summary: {
+ trace_id: "trace-1",
+ total_count: 1,
+ order: ["sequence_no_asc", "id_asc"],
+ },
+ }),
+ { status: 200, headers: { "Content-Type": "application/json" } },
+ ),
+ );
+
+ await listTraces("https://api.example.com", "user-1");
+ await getTraceDetail("https://api.example.com", "trace-1", "user-1");
+ await getTraceEvents("https://api.example.com", "trace-1", "user-1");
+
+ expect(fetchMock.mock.calls).toEqual([
+ [
+ "https://api.example.com/v0/traces?user_id=user-1",
+ expect.objectContaining({
+ cache: "no-store",
+ headers: expect.objectContaining({ "Content-Type": "application/json" }),
+ }),
+ ],
+ [
+ "https://api.example.com/v0/traces/trace-1?user_id=user-1",
+ expect.objectContaining({
+ cache: "no-store",
+ }),
+ ],
+ [
+ "https://api.example.com/v0/traces/trace-1/events?user_id=user-1",
+ expect.objectContaining({
+ cache: "no-store",
+ }),
+ ],
+ ]);
+ });
});
diff --git a/apps/web/lib/api.ts b/apps/web/lib/api.ts
index e96b945..0102866 100644
--- a/apps/web/lib/api.ts
+++ b/apps/web/lib/api.ts
@@ -117,6 +117,40 @@ export type TaskStepListSummary = {
order: string[];
};
+export type TraceReviewSummaryItem = {
+ id: string;
+ thread_id: string;
+ kind: string;
+ compiler_version: string;
+ status: string;
+ created_at: string;
+ trace_event_count: number;
+};
+
+export type TraceReviewItem = TraceReviewSummaryItem & {
+ limits: Record
;
+};
+
+export type TraceReviewEventItem = {
+ id: string;
+ trace_id: string;
+ sequence_no: number;
+ kind: string;
+ payload: unknown;
+ created_at: string;
+};
+
+export type TraceReviewListSummary = {
+ total_count: number;
+ order: string[];
+};
+
+export type TraceReviewEventListSummary = {
+ trace_id: string;
+ total_count: number;
+ order: string[];
+};
+
export type ApprovalRequestPayload = {
user_id: string;
thread_id: string;
@@ -346,3 +380,30 @@ export function getTaskSteps(apiBaseUrl: string, taskId: string, userId: string)
{ user_id: userId },
);
}
+
+export function listTraces(apiBaseUrl: string, userId: string) {
+ return requestJson<{ items: TraceReviewSummaryItem[]; summary: TraceReviewListSummary }>(
+ apiBaseUrl,
+ "/v0/traces",
+ undefined,
+ { user_id: userId },
+ );
+}
+
+export function getTraceDetail(apiBaseUrl: string, traceId: string, userId: string) {
+ return requestJson<{ trace: TraceReviewItem }>(
+ apiBaseUrl,
+ `/v0/traces/${traceId}`,
+ undefined,
+ { user_id: userId },
+ );
+}
+
+export function getTraceEvents(apiBaseUrl: string, traceId: string, userId: string) {
+ return requestJson<{ items: TraceReviewEventItem[]; summary: TraceReviewEventListSummary }>(
+ apiBaseUrl,
+ `/v0/traces/${traceId}/events`,
+ undefined,
+ { user_id: userId },
+ );
+}
diff --git a/apps/web/lib/fixtures.ts b/apps/web/lib/fixtures.ts
index 46b3d0f..43d4e9c 100644
--- a/apps/web/lib/fixtures.ts
+++ b/apps/web/lib/fixtures.ts
@@ -7,6 +7,7 @@ import type {
TaskStepListSummary,
ToolRecord,
} from "./api";
+import type { TraceItem } from "../components/trace-list";
const PURCHASE_TOOL: ToolRecord = {
id: "22222222-2222-4222-8222-222222222222",
@@ -28,6 +29,178 @@ const PURCHASE_TOOL: ToolRecord = {
const THREAD_MAGNESIUM = "11111111-1111-4111-8111-111111111111";
const THREAD_VITAMIN_D = "11111111-1111-4111-8111-111111111112";
+export const traceFixtures: TraceItem[] = [
+ {
+ id: "trace-ctx-401",
+ kind: "context.compile",
+ status: "completed",
+ title: "Context compile review",
+ summary:
+ "Compiled prior task state, admitted memories, and recent thread continuity before assistant response assembly.",
+ eventCount: 3,
+ createdAt: "2026-03-17T08:45:00Z",
+ source: "continuity_v0",
+ scope: "Thread magnesium review",
+ related: {
+ threadId: "thread-magnesium",
+ compilerVersion: "continuity_v0",
+ },
+ metadata: [
+ "Trace: trace-ctx-401",
+ "Thread: thread-magnesium",
+ "Compiler: continuity_v0",
+ "Status: completed",
+ "Limit max_sessions: 3",
+ "Limit max_events: 8",
+ ],
+ evidence: [
+ "Memory evidence admitted for supplement preference and merchant history.",
+ "Recent approval state included as part of the continuity pack.",
+ "Task-step lineage referenced before response generation.",
+ ],
+ events: [
+ {
+ id: "event-1",
+ kind: "compiler.scope",
+ title: "Scope resolved",
+ detail: "Single-user thread scope and compile limits were established for the request.",
+ facts: ["Sequence 1", "Captured at Mar 17, 08:45"],
+ },
+ {
+ id: "event-2",
+ kind: "memory.retrieve",
+ title: "Memory evidence attached",
+ detail: "Preference and purchase-history memories were ranked into the response context pack.",
+ facts: ["Sequence 2", "Captured at Mar 17, 08:45"],
+ },
+ {
+ id: "event-3",
+ kind: "task.retrieve",
+ title: "Task lifecycle linked",
+ detail: "Open task and step state were included so the answer could acknowledge the approval dependency.",
+ facts: ["Sequence 3", "Captured at Mar 17, 08:45"],
+ },
+ ],
+ detailSource: "fixture",
+ eventSource: "fixture",
+ },
+ {
+ id: "trace-approval-101",
+ kind: "approval.request",
+ status: "requires_review",
+ title: "Approval request review",
+ summary:
+ "Routing required user approval before the merchant proxy could execute the purchase request.",
+ eventCount: 3,
+ createdAt: "2026-03-17T06:50:00Z",
+ source: "approval_request_v0",
+ scope: "Supplement purchase review",
+ related: {
+ threadId: "thread-magnesium",
+ taskId: "task-201",
+ approvalId: "approval-101",
+ compilerVersion: "approval_request_v0",
+ },
+ metadata: [
+ "Trace: trace-approval-101",
+ "Thread: thread-magnesium",
+ "Task: task-201",
+ "Approval: approval-101",
+ "Compiler: approval_request_v0",
+ "Status: requires_review",
+ ],
+ evidence: [
+ "Policy rule marked purchase actions as approval-gated.",
+ "Tool metadata matched the requested action and scope.",
+ "Task-step trace link points back to the original governed request.",
+ ],
+ events: [
+ {
+ id: "event-4",
+ kind: "tool.route",
+ title: "Routing completed",
+ detail: "The merchant proxy was selected as the governing tool for the request.",
+ facts: ["Sequence 1", "Captured at Mar 17, 06:50"],
+ },
+ {
+ id: "event-5",
+ kind: "approval.state",
+ title: "Approval opened",
+ detail: "Approval record persisted with pending resolution state and task-step linkage.",
+ facts: ["Sequence 2", "Captured at Mar 17, 06:50"],
+ },
+ {
+ id: "event-6",
+ kind: "task.lifecycle",
+ title: "Task updated",
+ detail: "Task lifecycle moved into a pending approval state while retaining request provenance.",
+ facts: ["Sequence 3", "Captured at Mar 17, 06:50"],
+ },
+ ],
+ detailSource: "fixture",
+ eventSource: "fixture",
+ },
+ {
+ id: "trace-exec-311",
+ kind: "tool.proxy.execute",
+ status: "completed",
+ title: "Proxy execution review",
+ summary:
+ "Approved supplement purchase request executed through the proxy handler with task and trace linkage preserved.",
+ eventCount: 3,
+ createdAt: "2026-03-16T14:24:00Z",
+ source: "proxy_execution_v0",
+ scope: "Supplement execution review",
+ related: {
+ threadId: "thread-vitamin-d",
+ taskId: "task-182",
+ approvalId: "approval-100",
+ executionId: "execution-311",
+ compilerVersion: "proxy_execution_v0",
+ },
+ metadata: [
+ "Trace: trace-exec-311",
+ "Thread: thread-vitamin-d",
+ "Task: task-182",
+ "Approval: approval-100",
+ "Execution: execution-311",
+ "Compiler: proxy_execution_v0",
+ "Status: completed",
+ ],
+ evidence: [
+ "Execution occurred only after approval resolution.",
+ "Handler output and trace references stayed attached to the governed action record.",
+ "Task and task-step lifecycle traces were appended alongside execution status.",
+ ],
+ events: [
+ {
+ id: "event-7",
+ kind: "approval.check",
+ title: "Approval validated",
+ detail: "Execution preflight confirmed the approval was in an executable state.",
+ facts: ["Sequence 1", "Captured at Mar 16, 14:24"],
+ },
+ {
+ id: "event-8",
+ kind: "budget.check",
+ title: "Budget check passed",
+ detail: "Execution budget constraints did not block the governed action.",
+ facts: ["Sequence 2", "Captured at Mar 16, 14:24"],
+ },
+ {
+ id: "event-9",
+ kind: "execution.result",
+ title: "Handler completed",
+ detail:
+ "Proxy output was recorded for the approved supplement reorder with a linked execution trace and task-step status update.",
+ facts: ["Sequence 3", "Captured at Mar 16, 14:24"],
+ },
+ ],
+ detailSource: "fixture",
+ eventSource: "fixture",
+ },
+];
+
export const requestHistoryFixtures: RequestHistoryEntry[] = [
{
id: "trace-request-101",
@@ -332,6 +505,10 @@ export function getFixtureApproval(approvalId: string) {
return approvalFixtures.find((item) => item.id === approvalId) ?? null;
}
+export function getFixtureTrace(traceId: string) {
+ return traceFixtures.find((item) => item.id === traceId) ?? null;
+}
+
export function getFixtureTask(taskId: string) {
return taskFixtures.find((item) => item.id === taskId) ?? null;
}