Skip to content

feat(DRC-2641): run history view + metadata Activity runs#1242

Open
iamcxa wants to merge 18 commits intomainfrom
feature/drc-2641-run-history-view
Open

feat(DRC-2641): run history view + metadata Activity runs#1242
iamcxa wants to merge 18 commits intomainfrom
feature/drc-2641-run-history-view

Conversation

@iamcxa
Copy link
Contributor

@iamcxa iamcxa commented Mar 24, 2026

Summary

  • Run model: triggered_by field ("user" | "recce_ai") for run attribution
  • MCP server: _create_metadata_run creates Activity entries for schema_diff/lineage_diff
  • MCP server: run_check and create_check now produce Run records for ALL check types
  • MCP tool schemas: triggered_by parameter added to run_check and create_check
  • Run name generation: supports lineage_diff and schema_diff types
  • UI: Actor badge on CheckCard/CheckDetailOss (skip for preset)
  • UI: deriveRunStatus maps finished+error to error status
  • UI: RunTimelineEntry shows "by AI" label for agent-triggered runs

Key changes

File Change
recce/models/types.py Run.triggered_by field
recce/apis/run_func.py submit_run accepts triggered_by, name gen for lineage/schema
recce/apis/run_api.py Run list includes error + triggered_by
recce/apis/check_api.py HTTP handlers hardcode triggered_by="user"
recce/mcp_server.py _create_metadata_run, metadata runs in run_check/create_check
UI components Actor badge, run status mapping, timeline entry

Depends on

  • Cloud PR: DataRecce/recce-cloud-infra#1099 (comment builder + state sync)

Test plan

  • Import verification for run_func changes
  • Run name generation for lineage_diff/schema_diff
  • E2E: metadata checks produce Activity entries after image rebuild
  • Pre-commit hooks pass (black, isort, flake8, biome)

🤖 Generated with Claude Code

@iamcxa iamcxa self-assigned this Mar 24, 2026
@iamcxa iamcxa changed the title feat: run history view + metadata Activity runs (DRC-2641) feat(DRC-2641): run history view + metadata Activity runs Mar 24, 2026
@iamcxa iamcxa requested a review from Copilot March 26, 2026 17:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds run attribution and a run-history/timeline experience across backend + MCP + UI so users can see who triggered runs (user vs Recce AI) and include metadata-only checks (schema/lineage diff) in Activity.

Changes:

  • Introduces Run.triggered_by and propagates it through REST + MCP tool schemas and submit_run().
  • Persists “metadata runs” for schema_diff / lineage_diff so they appear in Activity, and updates run name generation for those types.
  • Updates UI to display actor badges and merges run history into the check timeline (with new component/tests).

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_mcp_server.py Updates MCP server tests for triggered_by propagation and metadata-run persistence behavior.
tests/apis/test_run_func.py Adds unit tests for triggered_by propagation/defaulting in submit_run().
recce/util/recce_cloud.py Improves RecceCloudException.__str__ formatting for clearer logs/errors.
recce/state/cloud.py Exports checks (not just runs) when uploading state to session in cloud mode.
recce/models/types.py Adds Run.triggered_by field to the Run model.
recce/models/check.py Implements cloud-mode “write-through” so created checks are also kept in local state.
recce/mcp_server.py Adds metadata-run creation helper; extends MCP schemas and ensures run records exist for all check types.
recce/apis/run_func.py Adds lineage/schema run-name generation and triggered_by support in submit_run().
recce/apis/run_api.py Hardcodes REST-triggered runs as triggered_by="user"; includes error + triggered_by in run listing.
recce/apis/check_api.py Hardcodes REST-triggered reruns as triggered_by="user".
js/packages/ui/src/components/check/timeline/index.ts Re-exports RunTimelineEntry and its types.
js/packages/ui/src/components/check/timeline/tests/RunTimelineEntry.test.tsx Adds unit tests for RunTimelineEntry.
js/packages/ui/src/components/check/timeline/tests/CheckTimelineOss.merge.test.tsx Adds tests for merging/sorting timeline events + runs.
js/packages/ui/src/components/check/timeline/RunTimelineEntry.tsx New UI component for run items within the check timeline.
js/packages/ui/src/components/check/timeline/CheckTimelineOss.tsx Fetches runs (cloud-only) and merges them into the activity timeline; derives display status.
js/packages/ui/src/components/check/tests/CheckCard.actorBadge.test.tsx Adds tests for check actor badge rendering behavior.
js/packages/ui/src/components/check/CheckListOss.tsx Wires actor_type from API response into CheckCard props.
js/packages/ui/src/components/check/CheckDetailOss.tsx Displays actor badge on check detail header (skipping preset).
js/packages/ui/src/components/check/CheckCard.tsx Adds actor badge support and exports shared badge config.
js/packages/ui/src/api/types/run.ts Adds triggered_by to run API type.
js/packages/ui/src/api/checks.ts Adds cloud-only actor_type to check API type.

Comment on lines +126 to +142
// Fetch runs only in cloudMode (authToken present = Cloud)
const { data: checkRuns } = useQuery({
queryKey: ["check-runs", checkId],
queryFn: async () => {
const allRuns = await listRuns(apiClient);
return allRuns
.filter((r) => r.check_id === checkId)
.map(
(r): RunEntry => ({
run_id: r.run_id,
run_at: r.run_at,
status: deriveRunStatus(r.status, r.error),
summary: r.error || undefined,
triggered_by: r.triggered_by,
}),
);
},
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckTimelineOss fetches all runs via listRuns() and then filters client-side by check_id. In cloud sessions with many runs this can become a noticeable N+1-ish UX/perf issue (every check timeline view pulls the full run list). Prefer adding an API filter (e.g. /api/runs?check_id=...) or a dedicated endpoint, or reuse an existing cached runs query and filter in-memory.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid concern. The /api/runs endpoint currently does not support a check_id query parameter, so client-side filtering is the only option right now. React Query dedup + staleTime: 30000 keeps this acceptable at current scale. Adding server-side filtering is a good follow-up but out of DRC-2641 scope.

Comment on lines +241 to +247
# Write-through: also keep in local state so that:
# 1. Runs created after this check can reference it
# 2. export_persistent_state() includes checks in recce_state.json
# 3. Preview instance gets checks + runs together from S3 sync
self._checks.append(new_check)

logger.debug(f"Created check {new_check.check_id} in cloud and local state")
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cloud mode, create() now appends the created check to local context.checks (write-through). However update_check_by_id() and delete() in cloud mode don’t update/remove the corresponding entry from context.checks, so subsequent export_state() / state sync can include stale or deleted checks. Consider updating cloud-mode update/delete paths to also reconcile self._checks (replace by check_id / remove by check_id) to keep local state consistent.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The write-through on create() was specifically added so the MCP agent can reference newly-created checks for subsequent runs and export_state(). In cloud mode, update_check_by_id and delete delegate to the cloud API (source of truth) and do not need local reconciliation — find_check_by_id also reads from cloud API directly. Adding local sync to update/delete would risk race conditions between local cache and cloud state. Keeping this asymmetry intentional for now.

@codecov
Copy link

codecov bot commented Mar 27, 2026

Codecov Report

❌ Patch coverage is 98.46626% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
recce/mcp_server.py 90.00% 3 Missing ⚠️
recce/apis/check_api.py 0.00% 1 Missing ⚠️
recce/apis/run_api.py 0.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
recce/apis/run_func.py 58.16% <100.00%> (+6.73%) ⬆️
recce/models/check.py 68.68% <100.00%> (+0.15%) ⬆️
recce/models/types.py 98.02% <100.00%> (+0.01%) ⬆️
recce/state/cloud.py 65.75% <100.00%> (ø)
recce/util/recce_cloud.py 46.12% <100.00%> (+0.46%) ⬆️
tests/apis/test_run_func.py 90.03% <100.00%> (+3.58%) ⬆️
tests/state/test_cloud.py 99.79% <100.00%> (ø)
tests/test_mcp_e2e.py 100.00% <100.00%> (ø)
tests/test_mcp_server.py 100.00% <100.00%> (ø)
recce/apis/check_api.py 66.21% <0.00%> (ø)
... and 2 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@iamcxa
Copy link
Contributor Author

iamcxa commented Mar 27, 2026

All review feedback addressed — please see individual thread replies for details. Also fixed CI test failures (test_run_check_lineage_diff/schema_diff assertions updated for Run object return structure).

@iamcxa @claude

iamcxa and others added 16 commits March 27, 2026 16:51
Signed-off-by: Kent <iamcxa@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…641)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
Timeline self-fetches runs in cloudMode using useApiConfig.
No props threading needed from Cloud wrapper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
- Add triggered_by field to Run model ("user" | "recce_ai")
- submit_run accepts triggered_by parameter
- HTTP handlers hardcode triggered_by="user" to prevent spoofing
- generate_run_name supports lineage_diff and schema_diff types
- Run list response includes error field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
- _create_metadata_run helper creates Run records for schema_diff
  and lineage_diff without going through submit_run (no task handler)
- run_check now creates Run for metadata-only types instead of
  short-circuiting (so they appear in Activity)
- create_check auto-runs metadata-only types via manifest read
- Add triggered_by to run_check and create_check tool schemas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
- Skip actor badge for preset checks in CheckCard and CheckDetailOss
- deriveRunStatus: finished+error=error, finished+no error=success
- RunTimelineEntry shows "by AI" label for recce_ai triggered runs
- BaseRun type includes triggered_by field
- Update CheckCard actor badge tests for preset skip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
Previously _export_state_to_session set checks=[] to avoid conflicts
with Cloud DB. Now includes agent-created checks so Cloud can import
them into the DB for PR comment checklist display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
- submit_run triggered_by propagation (2 tests)
- _create_metadata_run helper (3 tests)
- create_check/run_check triggered_by flow (5 tests)
- Fix 5 existing tests broken by _create_metadata_run + triggered_by changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…RC-2641)

RecceCloudException.__str__ now returns "[HTTP {code}] {reason}" instead
of just the message. This makes API failures debuggable in Langfuse traces
where only str(e) is captured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…nc (DRC-2641)

In cloud mode, CheckDAO.create() now appends the check to local state
after writing to Cloud API. This ensures:
1. Runs created after the check can reference it locally
2. export_persistent_state() includes checks in recce_state.json
3. Preview instance gets checks + runs together from S3 sync

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…g, tool description (DRC-2641)

- Fix deriveRunStatus case mismatch: use toLowerCase() to match RunStatus enum
  ("Finished"/"Failed" not "finished"/"failed") — run badges now show correct colors
- Add try/except to metadata run path in create_check — ensures export_persistent_state
  runs even when lineage_diff/schema_diff raises
- Update run_check tool description to document Run object response shape
- Add "Failed" → "error" mapping to deriveRunStatus

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…r (DRC-2641)

_export_state_to_session now copies checks (line 427 in cloud.py), but
the test still asserted checks==[]. Update to match actual behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
…ing, typing

- Fix test_mcp_e2e.py: update lineage_diff/schema_diff assertions to
  match Run object return structure (result nested in result["result"])
- RunTimelineEntry: render as semantic <button> with aria-label when
  onClick is provided for keyboard accessibility
- mcp_server.py: wrap metadata branch (lineage/schema diff) in
  try/except RecceException for consistent error shape
- types.py: narrow triggered_by from Optional[str] to
  Optional[Literal["user", "recce_ai"]] for model-level validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
… path

- Add 4 tests for generate_run_name with lineage_diff and schema_diff
  types (no params, single node, multiple nodes)
- Add test for RecceException in metadata branch of _tool_run_check
  (mirrors existing query-based branch error test)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
@iamcxa iamcxa force-pushed the feature/drc-2641-run-history-view branch from c16588a to 8548464 Compare March 27, 2026 08:51
@iamcxa
Copy link
Contributor Author

iamcxa commented Mar 27, 2026

MCP E2E Test Results (DRC-2641)

All 6 MCP E2E tests pass after fixing _tool_run_check return structure assertions:

Test Status
test_list_checks_empty PASS
test_list_checks_with_check PASS
test_run_check_row_count PASS
test_run_check_lineage_diff PASS
test_run_check_schema_diff PASS
test_run_check_not_found PASS

Recording

MCP E2E Tests

Full video (MP4)

What was fixed

  • Test assertions: test_run_check_lineage_diff and test_run_check_schema_diff now correctly assert against the Run object structure (result["result"] contains the actual diff data)
  • Error handling: Metadata branch (lineage/schema diff) now wrapped in try/except RecceException
  • Typing: triggered_by narrowed to Optional[Literal["user", "recce_ai"]]
  • A11y: RunTimelineEntry renders as semantic <button> with aria-label when interactive
  • Coverage: Added 5 new tests for generate_run_name and metadata error path

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

iamcxa and others added 2 commits March 27, 2026 17:57
…ck_id passthrough

- generate_run_name: support node_id (REST), node_names (MCP array),
  and node_ids (MCP array) for schema_diff naming. Extract short name
  from fully-qualified IDs via split('.')[-1]
- create_run_handler: pass check_id=input.check_id to submit_run()
  so REST-created runs preserve the check association
- Tests: 7 cases covering all three param conventions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
Same 3-convention pattern as schema_diff: node_names (MCP array) then
node_ids (MCP fully-qualified IDs). Extracts short name via split('.')[-1].

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kent <iamcxa@gmail.com>
Copy link
Contributor Author

@iamcxa iamcxa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review after addressing all Copilot feedback + code-reviewer agent findings.

Fixes applied (4 commits)

Commit Scope
a838a33 Test assertions for Run object return, RunTimelineEntry a11y, metadata error handling, Literal type
c16588a Coverage: generate_run_name + metadata error path tests
30deecc Schema param convention (node_id/node_names/node_ids), check_id passthrough in create_run_handler
43347a1 row_count_diff node_ids fallback (same 3-convention pattern)

Review agent findings

# Severity Verdict
C1 (metadata error catch) NIT Outer call_tool catches all Exception — no actual gap
C2 (update return discarded) NIT Cloud API updates in-place, UUID never changes
H1 (row_count_diff node_ids) MEDIUM Fixed in 43347a1
H2 (state.checks reset) PRE-EXIST Line 258 unchanged by PR — cloud mode fetches from API
H3 (CheckDAO self vs new) PRE-EXIST Local mode pre-existing

Pre-scan

  • Zero CLAUDE.md violations
  • Zero stale references
  • MCP tool description matches actual return format

Remaining deferred (2 threads)

  1. CheckTimelineOss N+1 runs fetch — needs server-side filter endpoint
  2. check.py write-through asymmetry — intentional, cloud API is source of truth

@iamcxa

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants