Skip to content

feat(replay-vision): API validation + lens_result row column#58685

Open
TueHaulund wants to merge 1 commit into
tue/replay-vision-call-providerfrom
tue/replay-vision-lens-validation
Open

feat(replay-vision): API validation + lens_result row column#58685
TueHaulund wants to merge 1 commit into
tue/replay-vision-call-providerfrom
tue/replay-vision-lens-validation

Conversation

@TueHaulund
Copy link
Copy Markdown
Contributor

@TueHaulund TueHaulund commented May 17, 2026

Problem

Two API gaps surfaced as workflow-runtime errors instead of 400s, and FE rendering required a CH query for output that should live on the row.

Changes

Validation: lens_config is validated against the per-lens_type Pydantic discriminated union (AnyLens) — a classifier without tags, a scorer with an inverted scale, or a non-dict config now 400s on save. query is validated against RecordingsQuery (strict, extra="forbid") and typed for OpenAPI via @extend_schema_field; date_from/date_to are stripped on save. All three persisted blobs (query, lens_snapshot, lens_result) are re-validated on read at the API serializer boundary — graceful degradation (log + null) on validation failure.

lens_result JSONField on ReplayObservation: populated by mark_observation_succeeded_activity. Typed via a new LensResult { model_output: AnyLensOutput, signals_count: int } Pydantic model. AnyLensOutput is a discriminated union over the five output classes; each output class declares lens_type: Literal[LensType.X] = LensType.X as the discriminator. Gemini's structured-output mode sees the literal constraint and is forced to emit the correct value; Pydantic validation catches any deviation. Adds BaseLensOutput.to_event_properties() helper.

Cleanup: removes the dead backend/presentation/ and backend/logic/ bootstrap scaffolds (product is not isolated, no facade pattern).

How did you test this code?

Agent-authored. Automated tests only.

160 tests pass under hogli test products/replay_vision/backend/tests/. Coverage includes valid/invalid lens_config per lens type, lens-type PATCH validation, query validation + date_from/date_to stripping, lens_result round-trip per type, to_event_properties flattening + lens_type exclusion.

Publish to changelog?

no

🤖 Agent context

Authored by Claude (Opus 4.7) via Claude Code. Stacked on tue/replay-vision-call-provider (PR #58607).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

🔍 Migration Risk Analysis

We've analyzed your migrations for potential risks.

Summary: 0 Safe | 1 Needs Review | 1 Blocked

❌ Blocked

Causes locks or breaks compatibility

replay_vision.0003_remove_replayobservation_lens_config_snapshot_and_more
  │  └─ #1 ❌ RemoveField
  │     Dropping column breaks backwards compatibility and can't rollback
  │     model: replayobservation, field: lens_config_snapshot
  │  └─ #2 ❌ RemoveField
  │     Dropping column breaks backwards compatibility and can't rollback
  │     model: replayobservation, field: lens_version
  │  └─ #3 ❌ RemoveField
  │     Dropping column breaks backwards compatibility and can't rollback
  │     model: replayobservation, field: model_used
  │  └─ #4 ❌ RemoveField
  │     Dropping column breaks backwards compatibility and can't rollback
  │     model: replayobservation, field: provider_used
  │  └─ #5 ⚠️ AddField
  │     Adding NOT NULL field with callable default (dict) - verify it's stable
  │     model: replayobservation, field: lens_snapshot, default: dict
  │
  └──> �[91m⚠️  COMBINATION RISKS:�[0m
       ❌ BLOCKED: Multiple high-risk operations in one migration: #1
       RemoveField, #2 RemoveField, #3 RemoveField, #4 RemoveField
       Each high-risk operation (score 4+) should be isolated to make
       rollback easier and reduce deployment risk. Consider splitting
       into separate migrations.

⚠️ Needs Review

May have performance impact

replay_vision.0004_replayobservation_lens_result
  └─ #1 ⚠️ AddField
     Adding NOT NULL field with callable default (dict) - verify it's stable
     model: replayobservation, field: lens_result, default: dict

📚 How to Deploy These Changes Safely

RemoveField:

Multi-phase column drop:

  1. Remove field from Django model (keeps column in DB)
  2. Wait at least one full deployment cycle
  3. Optionally drop column with RemoveField

See the migration safety guide

Last updated: 2026-05-17 15:19 UTC (d02bc21)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

Migration SQL Changes

Hey 👋, we've detected some migrations on this PR. Here's the SQL output for each migration, make sure they make sense:

products/replay_vision/backend/migrations/0004_replayobservation_lens_result.py

BEGIN;
--
-- Add field lens_result to replayobservation
--
ALTER TABLE "replay_vision_replayobservation" ADD COLUMN "lens_result" jsonb DEFAULT '{}'::jsonb NOT NULL;
ALTER TABLE "replay_vision_replayobservation" ALTER COLUMN "lens_result" DROP DEFAULT;
COMMIT;

Last updated: 2026-05-17 15:18 UTC (d02bc21)

@TueHaulund TueHaulund changed the title feat(replay-vision): validate lens_config and query at the API layer feat(replay-vision): API validation + lens_result row column May 17, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

Size Change: -5.16 kB (0%)

Total Size: 117 MB

📦 View Changed
Filename Size Change
frontend/dist/EndpointsScene 23.9 kB +2.14 kB (+9.81%) ⚠️
frontend/dist/SupportTicketScene 26.5 kB -7.3 kB (-21.61%) 🎉
ℹ️ View Unchanged
Filename Size Change
frontend/dist/368Hedgehogs 5.51 kB 0 B
frontend/dist/abap 14.2 kB 0 B
frontend/dist/AccountConnected 2.99 kB 0 B
frontend/dist/Action 24.9 kB +101 B (+0.41%)
frontend/dist/Actions 1.26 kB 0 B
frontend/dist/AdvancedActivityLogsScene 39.9 kB 0 B
frontend/dist/AgenticAuthorize 5.7 kB 0 B
frontend/dist/apex 3.99 kB 0 B
frontend/dist/ApprovalDetail 16.5 kB 0 B
frontend/dist/architecture-7EHR7CIX 372 B 0 B
frontend/dist/architectureDiagram-3BPJPVTR 151 kB 0 B
frontend/dist/array.full.es5.js 347 kB 0 B
frontend/dist/array.full.js 427 kB 0 B
frontend/dist/array.js 191 kB 0 B
frontend/dist/AsyncMigrations 13.4 kB 0 B
frontend/dist/AuthenticatedShell 173 kB 0 B
frontend/dist/AuthorizationStatus 969 B 0 B
frontend/dist/azcli 885 B 0 B
frontend/dist/bat 1.88 kB 0 B
frontend/dist/BatchExportScene 60.8 kB 0 B
frontend/dist/bicep 2.59 kB 0 B
frontend/dist/Billing 731 B 0 B
frontend/dist/BillingSection 21 kB 0 B
frontend/dist/blockDiagram-GPEHLZMM 72.5 kB 0 B
frontend/dist/BoxPlot 5.25 kB 0 B
frontend/dist/browserAll-0QZMN1W2 37.4 kB 0 B
frontend/dist/BusinessKnowledgeScene 18.9 kB +35 B (+0.19%)
frontend/dist/ButtonPrimitives 796 B +34 B (+4.46%)
frontend/dist/c4Diagram-AAUBKEIU 70.8 kB 0 B
frontend/dist/CalendarHeatMap 5.04 kB 0 B
frontend/dist/cameligo 2.23 kB 0 B
frontend/dist/changeRequestsLogic 782 B +34 B (+4.55%) 🔍
frontend/dist/classDiagram-4FO5ZUOK 1.28 kB 0 B
frontend/dist/classDiagram-v2-Q7XG4LA2 1.28 kB 0 B
frontend/dist/CLIAuthorize 11.6 kB 0 B
frontend/dist/CLILive 4.26 kB 0 B
frontend/dist/clojure 9.68 kB 0 B
frontend/dist/CodeEditorInline 696 B 0 B
frontend/dist/coffee 3.63 kB 0 B
frontend/dist/Cohort 27.7 kB 0 B
frontend/dist/CohortCalculationHistory 6.47 kB 0 B
frontend/dist/Cohorts 9.64 kB 0 B
frontend/dist/ConfirmOrganization 4.76 kB 0 B
frontend/dist/conversations.js 67.3 kB 0 B
frontend/dist/cose-bilkent-S5V4N54A 82.8 kB 0 B
frontend/dist/Coupons 963 B 0 B
frontend/dist/cpp 5.33 kB 0 B
frontend/dist/Create 898 B 0 B
frontend/dist/crisp-chat-integration.js 1.97 kB 0 B
frontend/dist/csharp 4.56 kB 0 B
frontend/dist/csp 1.45 kB 0 B
frontend/dist/css 4.54 kB 0 B
frontend/dist/cssMode 4.2 kB 0 B
frontend/dist/CustomCssScene 3.8 kB 0 B
frontend/dist/CustomerAnalyticsConfigurationScene 2.31 kB +36 B (+1.58%)
frontend/dist/CustomerAnalyticsScene 28.1 kB 0 B
frontend/dist/CustomerJourneyBuilderScene 2.08 kB 0 B
frontend/dist/CustomerJourneyTemplatesScene 7.75 kB 0 B
frontend/dist/customizations.full.js 18 kB 0 B
frontend/dist/CyclotronJobInputAssignee 1.57 kB 0 B
frontend/dist/CyclotronJobInputBusinessHours 2.92 kB -35 B (-1.18%)
frontend/dist/CyclotronJobInputTicketTags 920 B 0 B
frontend/dist/cypher 3.42 kB 0 B
frontend/dist/dagre-BM42HDAG 11.9 kB 0 B
frontend/dist/dart 4.29 kB 0 B
frontend/dist/Dashboard 1.38 kB 0 B
frontend/dist/Dashboards 21.8 kB 0 B
frontend/dist/DashboardTemplateCopyScene 5.95 kB 0 B
frontend/dist/DataManagementScene 884 B 0 B
frontend/dist/DataPipelinesNewScene 2.55 kB 0 B
frontend/dist/DataWarehouseScene 46.5 kB 0 B
frontend/dist/Deactivated 1.37 kB 0 B
frontend/dist/dead-clicks-autocapture.js 13.2 kB 0 B
frontend/dist/DeadLetterQueue 5.63 kB 0 B
frontend/dist/DebugScene 20.2 kB 0 B
frontend/dist/decompressionWorker 2.85 kB 0 B
frontend/dist/decompressionWorker.js 2.85 kB 0 B
frontend/dist/DecompressionWorkerManager 329 B 0 B
frontend/dist/DefinitionEdit 8.82 kB 0 B
frontend/dist/DefinitionView 24.3 kB 0 B
frontend/dist/Deployment 3.9 kB -35 B (-0.89%)
frontend/dist/DeploymentProject 5.47 kB +35 B (+0.64%)
frontend/dist/Deployments 9.17 kB 0 B
frontend/dist/DestinationsScene 2.92 kB 0 B
frontend/dist/diagram-2AECGRRQ 6.66 kB 0 B
frontend/dist/diagram-5GNKFQAL 3.61 kB 0 B
frontend/dist/diagram-KO2AKTUF 11.5 kB 0 B
frontend/dist/diagram-LMA3HP47 5.02 kB 0 B
frontend/dist/diagram-OG6HWLK6 11.8 kB 0 B
frontend/dist/dist 643 B 0 B
frontend/dist/dockerfile 1.91 kB 0 B
frontend/dist/EarlyAccessFeature 855 B -136 B (-13.72%) 👏
frontend/dist/EarlyAccessFeatures 3.09 kB 0 B
frontend/dist/ecl 5.38 kB 0 B
frontend/dist/EditorScene 1.38 kB 0 B
frontend/dist/elixir 10.3 kB 0 B
frontend/dist/elk.bundled 1.44 MB 0 B
frontend/dist/EmailMFAVerify 3.26 kB 0 B
frontend/dist/EndpointScene 39.9 kB +35 B (+0.09%)
frontend/dist/erDiagram-TEJ5UH35 27.7 kB 0 B
frontend/dist/ErrorTrackingIssueFingerprintsScene 7.22 kB 0 B
frontend/dist/ErrorTrackingIssueScene 101 kB +979 B (+0.98%)
frontend/dist/ErrorTrackingScene 27 kB 0 B
frontend/dist/EvaluationTemplates 813 B +34 B (+4.36%)
frontend/dist/eventmodeling-FCH6USID 375 B 0 B
frontend/dist/EventsScene 2.81 kB 0 B
frontend/dist/exception-autocapture.js 11.8 kB 0 B
frontend/dist/Experiment 210 kB 0 B
frontend/dist/Experiments 19.3 kB 0 B
frontend/dist/exporter 18.9 kB 0 B
frontend/dist/exporter.js 18.9 kB 0 B
frontend/dist/ExporterDashboardScene 1.78 kB 0 B
frontend/dist/ExporterHeatmapScene 19.5 kB 0 B
frontend/dist/ExporterInsightScene 2.88 kB 0 B
frontend/dist/ExporterInterviewScene 307 kB 0 B
frontend/dist/ExporterNotebookScene 2.7 MB 0 B
frontend/dist/ExporterRecordingScene 995 B 0 B
frontend/dist/exporterSharedChunkAnchors 288 kB 0 B
frontend/dist/exporterSharedChunkAnchors.js 288 kB 0 B
frontend/dist/ExportsScene 4.23 kB 0 B
frontend/dist/FeatureFlag 134 kB 0 B
frontend/dist/FeatureFlags 844 B 0 B
frontend/dist/FeatureFlagTemplatesScene 7.24 kB 0 B
frontend/dist/FlappyHog 5.99 kB -34 B (-0.56%)
frontend/dist/flow9 1.85 kB 0 B
frontend/dist/flowDiagram-I6XJVG4X 61.6 kB 0 B
frontend/dist/freemarker2 16.7 kB 0 B
frontend/dist/fsharp 3.02 kB 0 B
frontend/dist/ganttDiagram-6RSMTGT7 50.9 kB 0 B
frontend/dist/gitGraph-WXDBUCRP 360 B 0 B
frontend/dist/gitGraphDiagram-PVQCEYII 30.2 kB 0 B
frontend/dist/go 2.69 kB 0 B
frontend/dist/graphql 2.3 kB 0 B
frontend/dist/Group 14.9 kB 0 B
frontend/dist/Groups 4.15 kB 0 B
frontend/dist/GroupsNew 7.58 kB 0 B
frontend/dist/handlebars 7.38 kB 0 B
frontend/dist/hcl 3.63 kB 0 B
frontend/dist/HealthCategoryDetailScene 7.48 kB 0 B
frontend/dist/HealthScene 12.4 kB 0 B
frontend/dist/HeatmapNewScene 5.25 kB 0 B
frontend/dist/HeatmapRecordingScene 4.25 kB 0 B
frontend/dist/HeatmapScene 6.8 kB 0 B
frontend/dist/HeatmapsScene 4.13 kB 0 B
frontend/dist/hls 394 kB 0 B
frontend/dist/HogFunctionScene 59.5 kB 0 B
frontend/dist/hogql_parser_wasm_browser 1.52 MB 0 B
frontend/dist/HogRepl 7.61 kB 0 B
frontend/dist/html 5.62 kB 0 B
frontend/dist/htmlMode 4.65 kB 0 B
frontend/dist/image-blob-reduce.esm 49.5 kB 0 B
frontend/dist/InboxScene 59.9 kB 0 B
frontend/dist/index 60.5 kB 0 B
frontend/dist/index.js 60.5 kB 0 B
frontend/dist/info-J43DQDTF 348 B 0 B
frontend/dist/infoDiagram-5YYISTIA 1.32 kB 0 B
frontend/dist/ini 1.14 kB 0 B
frontend/dist/InsightQuickStart 5.67 kB 0 B
frontend/dist/InsightScene 34.6 kB 0 B
frontend/dist/IntegrationsRedirect 976 B 0 B
frontend/dist/intercom-integration.js 2.03 kB 0 B
frontend/dist/InviteSignup 15.2 kB 0 B
frontend/dist/ishikawaDiagram-YF4QCWOH 18 kB 0 B
frontend/dist/java 3.26 kB 0 B
frontend/dist/javascript 1.02 kB 0 B
frontend/dist/journeyDiagram-JHISSGLW 24 kB 0 B
frontend/dist/jsonMode 13.9 kB 0 B
frontend/dist/julia 7.26 kB 0 B
frontend/dist/kanban-definition-UN3LZRKU 21.2 kB 0 B
frontend/dist/katex 266 kB 0 B
frontend/dist/kotlin 3.44 kB 0 B
frontend/dist/lazy 146 kB 0 B
frontend/dist/LegacyPluginScene 20.9 kB 0 B
frontend/dist/LegalDocumentNewScene 59.6 kB 0 B
frontend/dist/LegalDocumentsScene 5.17 kB -35 B (-0.67%)
frontend/dist/LemonTextAreaMarkdown 740 B 0 B
frontend/dist/less 3.93 kB 0 B
frontend/dist/lexon 2.47 kB 0 B
frontend/dist/lib 2.25 kB 0 B
frontend/dist/Link 706 B 0 B
frontend/dist/LinkScene 25.1 kB +36 B (+0.14%)
frontend/dist/LinksScene 4.44 kB +36 B (+0.82%)
frontend/dist/liquid 4.57 kB 0 B
frontend/dist/LiveDebugger 19.3 kB 0 B
frontend/dist/LiveEventsTable 5.47 kB 0 B
frontend/dist/LLMAnalyticsClusterScene 21.4 kB 0 B
frontend/dist/LLMAnalyticsClustersScene 54.7 kB 0 B
frontend/dist/LLMAnalyticsDatasetScene 20.8 kB 0 B
frontend/dist/LLMAnalyticsDatasetsScene 3.52 kB +36 B (+1.03%)
frontend/dist/LLMAnalyticsEvaluation 59.6 kB 0 B
frontend/dist/LLMAnalyticsEvaluationsScene 28 kB -34 B (-0.12%)
frontend/dist/LLMAnalyticsPlaygroundScene 37.5 kB 0 B
frontend/dist/LLMAnalyticsScene 117 kB 0 B
frontend/dist/LLMAnalyticsSessionScene 13.6 kB +41 B (+0.3%)
frontend/dist/LLMAnalyticsTag 27.3 kB 0 B
frontend/dist/LLMAnalyticsTagsScene 7.19 kB +36 B (+0.5%)
frontend/dist/LLMAnalyticsTraceScene 130 kB 0 B
frontend/dist/LLMAnalyticsUsers 764 B +34 B (+4.66%) 🔍
frontend/dist/LLMASessionFeedbackDisplay 5.04 kB 0 B
frontend/dist/LLMPromptScene 17.7 kB 0 B
frontend/dist/LLMPromptsScene 4.68 kB 0 B
frontend/dist/LLMSkillScene 793 B 0 B
frontend/dist/LLMSkillsScene 844 B 0 B
frontend/dist/Login 8.86 kB 0 B
frontend/dist/Login2FA 4.49 kB 0 B
frontend/dist/logs.js 38.9 kB 0 B
frontend/dist/LogsAlertDetailScene 17.2 kB -32 B (-0.19%)
frontend/dist/LogsSamplingDetailScene 4.48 kB +36 B (+0.81%)
frontend/dist/LogsSamplingNewScene 1.97 kB 0 B
frontend/dist/LogsScene 17.7 kB 0 B
frontend/dist/lua 2.16 kB 0 B
frontend/dist/m3 2.85 kB 0 B
frontend/dist/main 819 kB 0 B
frontend/dist/ManagedMigration 14.4 kB 0 B
frontend/dist/markdown 3.83 kB 0 B
frontend/dist/MarketingAnalyticsScene 40.3 kB 0 B
frontend/dist/MaterializedColumns 10.4 kB 0 B
frontend/dist/Max 888 B 0 B
frontend/dist/mdx 5.43 kB 0 B
frontend/dist/memlens.lib.bundle 27.9 kB 0 B
frontend/dist/mermaid.core 28.5 kB 0 B
frontend/dist/MermaidDiagram 2.15 kB +40 B (+1.89%)
frontend/dist/MessageTemplate 16.5 kB 0 B
frontend/dist/MetricsScene 1.04 kB 0 B
frontend/dist/mindmap-definition-RKZ34NQL 24.8 kB 0 B
frontend/dist/mips 2.62 kB 0 B
frontend/dist/ModelsScene 14.6 kB 0 B
frontend/dist/MonacoDiffEditor 471 B 0 B
frontend/dist/monacoEditorWorker 288 kB 0 B
frontend/dist/monacoEditorWorker.js 288 kB 0 B
frontend/dist/monacoJsonWorker 419 kB 0 B
frontend/dist/monacoJsonWorker.js 419 kB 0 B
frontend/dist/monacoTsWorker 7.02 MB 0 B
frontend/dist/monacoTsWorker.js 7.02 MB 0 B
frontend/dist/MoveToPostHogCloud 4.7 kB 0 B
frontend/dist/msdax 4.95 kB 0 B
frontend/dist/mysql 11.3 kB 0 B
frontend/dist/NavTabChat 7.41 kB 0 B
frontend/dist/NewSourceScene 980 B -68 B (-6.49%)
frontend/dist/NewTabScene 1.72 kB 0 B
frontend/dist/NodeDetailScene 16.9 kB 0 B
frontend/dist/NotebookCanvasScene 3.61 kB 0 B
frontend/dist/NotebookPanel 5.58 kB 0 B
frontend/dist/NotebookScene 8.82 kB 0 B
frontend/dist/NotebooksScene 7.84 kB 0 B
frontend/dist/OAuthAuthorize 844 B 0 B
frontend/dist/objective-c 2.44 kB 0 B
frontend/dist/Onboarding 771 kB 0 B
frontend/dist/OnboardingCouponRedemption 1.44 kB 0 B
frontend/dist/packet-YPE3B663 354 B 0 B
frontend/dist/pascal 3.03 kB 0 B
frontend/dist/pascaligo 2.04 kB 0 B
frontend/dist/passkeyLogic 688 B -34 B (-4.71%)
frontend/dist/PasswordReset 4.6 kB 0 B
frontend/dist/PasswordResetComplete 3.23 kB 0 B
frontend/dist/PendingDeletion 2.38 kB 0 B
frontend/dist/perl 8.29 kB 0 B
frontend/dist/PersonScene 18.2 kB 0 B
frontend/dist/PersonsScene 5.91 kB 0 B
frontend/dist/pgsql 13.5 kB 0 B
frontend/dist/php 8.06 kB 0 B
frontend/dist/pie-LRSECV5Y 345 B 0 B
frontend/dist/pieDiagram-4H26LBE5 4.92 kB 0 B
frontend/dist/PipelineStatusScene 9.35 kB 0 B
frontend/dist/pla 1.72 kB 0 B
frontend/dist/posthog 146 kB 0 B
frontend/dist/postiats 7.9 kB 0 B
frontend/dist/powerquery 17 kB 0 B
frontend/dist/powershell 3.31 kB 0 B
frontend/dist/PreflightCheck 5.81 kB 0 B
frontend/dist/product-tours.js 115 kB 0 B
frontend/dist/ProductTour 275 kB 0 B
frontend/dist/ProductTours 4.92 kB 0 B
frontend/dist/ProjectHomepage 19.9 kB 0 B
frontend/dist/protobuf 9.09 kB 0 B
frontend/dist/pug 4.86 kB 0 B
frontend/dist/python 4.8 kB 0 B
frontend/dist/qsharp 3.23 kB 0 B
frontend/dist/quadrantDiagram-W4KKPZXB 34.4 kB 0 B
frontend/dist/QueryPerformance 8.86 kB 0 B
frontend/dist/r 3.16 kB 0 B
frontend/dist/radar-GUYGQ44K 351 B 0 B
frontend/dist/razor 9.38 kB 0 B
frontend/dist/react-json-view 121 kB 0 B
frontend/dist/recorder-v2.js 98.6 kB 0 B
frontend/dist/recorder.js 98.6 kB 0 B
frontend/dist/redis 3.59 kB 0 B
frontend/dist/redshift 11.8 kB 0 B
frontend/dist/RegionMap 29.7 kB +35 B (+0.12%)
frontend/dist/render-query 26.6 MB 0 B
frontend/dist/render-query.js 26.6 MB 0 B
frontend/dist/requirementDiagram-4Y6WPE33 31.9 kB 0 B
frontend/dist/ResourceTransfer 9.42 kB 0 B
frontend/dist/restructuredtext 3.94 kB 0 B
frontend/dist/RevenueAnalyticsScene 25.8 kB 0 B
frontend/dist/ruby 8.54 kB 0 B
frontend/dist/rust 4.2 kB 0 B
frontend/dist/sankeyDiagram-5OEKKPKP 24 kB 0 B
frontend/dist/SavedInsights 902 B 0 B
frontend/dist/sb 1.86 kB 0 B
frontend/dist/scala 7.36 kB 0 B
frontend/dist/schema 719 kB 0 B
frontend/dist/SchemaScene 21.2 kB 0 B
frontend/dist/scheme 1.8 kB 0 B
frontend/dist/scss 6.45 kB 0 B
frontend/dist/SdkDoctorScene 9.66 kB 0 B
frontend/dist/sequenceDiagram-3UESZ5HK 117 kB 0 B
frontend/dist/SessionAttributionExplorerScene 6.87 kB 0 B
frontend/dist/SessionGroupSummariesTable 4.84 kB 0 B
frontend/dist/SessionGroupSummaryScene 17.2 kB +36 B (+0.21%)
frontend/dist/SessionProfileScene 15.3 kB 0 B
frontend/dist/SessionRecordingDetail 2 kB 0 B
frontend/dist/SessionRecordingFilePlaybackScene 4.71 kB 0 B
frontend/dist/SessionRecordings 980 B 0 B
frontend/dist/SessionRecordingsKiosk 10.1 kB 0 B
frontend/dist/SessionRecordingsPlaylistScene 5.28 kB 0 B
frontend/dist/SessionRecordingsSettingsScene 2.14 kB 0 B
frontend/dist/SessionsScene 4.22 kB 0 B
frontend/dist/SettingsScene 3.23 kB 0 B
frontend/dist/sharedChunkAnchors 236 kB 0 B
frontend/dist/sharedChunkAnchors.js 236 kB 0 B
frontend/dist/SharedMetric 6.03 kB 0 B
frontend/dist/SharedMetrics 787 B 0 B
frontend/dist/shell 3.11 kB 0 B
frontend/dist/SignupContainer 28.4 kB 0 B
frontend/dist/Site 1.43 kB 0 B
frontend/dist/solidity 18.6 kB 0 B
frontend/dist/sophia 2.8 kB 0 B
frontend/dist/SourceScene 928 B 0 B
frontend/dist/SourcesScene 6.17 kB 0 B
frontend/dist/sparql 2.59 kB 0 B
frontend/dist/sql 10.3 kB 0 B
frontend/dist/SqlVariableEditScene 7.49 kB 0 B
frontend/dist/st 7.44 kB 0 B
frontend/dist/StartupProgram 21.4 kB 0 B
frontend/dist/stateDiagram-AJRCARHV 11.3 kB +5 B (+0.04%)
frontend/dist/stateDiagram-v2-BHNVJYJU 1.18 kB 0 B
frontend/dist/StripeConfirmInstall 3.78 kB 0 B
frontend/dist/SubscriptionScene 14.4 kB 0 B
frontend/dist/SubscriptionsScene 5.42 kB 0 B
frontend/dist/SupportSettingsScene 1.68 kB +35 B (+2.13%)
frontend/dist/SupportTicketsScene 937 B 0 B
frontend/dist/Survey 1.12 kB 0 B
frontend/dist/SurveyFormBuilder 1.78 kB 0 B
frontend/dist/Surveys 26.6 kB 0 B
frontend/dist/surveys.js 94.7 kB 0 B
frontend/dist/SurveyWizard 72.7 kB 0 B
frontend/dist/swift 5.3 kB 0 B
frontend/dist/SystemStatus 17.1 kB 0 B
frontend/dist/systemverilog 7.65 kB 0 B
frontend/dist/TaskDetailScene 23 kB 0 B
frontend/dist/TaskTracker 14.4 kB 0 B
frontend/dist/tcl 3.61 kB 0 B
frontend/dist/TextCardMarkdownEditor 11.2 kB 0 B
frontend/dist/timeline-definition-PNZ67QCA 31.3 kB 0 B
frontend/dist/toolbar 14.9 MB 0 B
frontend/dist/toolbar.js 14.9 MB 0 B
frontend/dist/ToolbarLaunch 2.71 kB 0 B
frontend/dist/tracing-headers.js 1.74 kB 0 B
frontend/dist/TracingScene 53.9 kB 0 B
frontend/dist/TransformationsScene 2.16 kB 0 B
frontend/dist/treemap-LRROVOQU 357 B 0 B
frontend/dist/treeView-BLDUP644 360 B 0 B
frontend/dist/TrendsBarChart 6.74 kB 0 B
frontend/dist/TrendsLineChart 6.8 kB 0 B
frontend/dist/tsMode 24 kB 0 B
frontend/dist/twig 6.01 kB 0 B
frontend/dist/TwoFactorReset 4.27 kB 0 B
frontend/dist/typescript 274 B 0 B
frontend/dist/typespec 2.86 kB 0 B
frontend/dist/Unsubscribe 1.9 kB 0 B
frontend/dist/UserInterview 7.76 kB -35 B (-0.45%)
frontend/dist/UserInterviewResponse 4.76 kB -34 B (-0.71%)
frontend/dist/UserInterviews 3.77 kB -35 B (-0.92%)
frontend/dist/vb 5.83 kB 0 B
frontend/dist/vennDiagram-CIIHVFJN 41.6 kB 0 B
frontend/dist/VercelConnect 5.23 kB 0 B
frontend/dist/VercelLinkError 2.5 kB 0 B
frontend/dist/VerifyEmail 5.02 kB 0 B
frontend/dist/vimMode 211 kB 0 B
frontend/dist/VisualReviewIndexScene 2.41 kB -36 B (-1.47%)
frontend/dist/VisualReviewRunScene 44.3 kB 0 B
frontend/dist/VisualReviewRunsScene 7.17 kB -38 B (-0.53%)
frontend/dist/VisualReviewSettingsScene 11 kB -35 B (-0.32%)
frontend/dist/VisualReviewSnapshotHistoryScene 12.4 kB 0 B
frontend/dist/VisualReviewSnapshotOverviewScene 17.5 kB -36 B (-0.21%)
frontend/dist/wardley-L42UT6IY 352 B 0 B
frontend/dist/wardleyDiagram-YWT4CUSO 26.2 kB 0 B
frontend/dist/web-vitals-with-attribution.js 11.8 kB 0 B
frontend/dist/web-vitals.js 6.39 kB 0 B
frontend/dist/WebAnalyticsScene 9.79 kB 0 B
frontend/dist/WebGLRenderer-DYjOwNoG 60.4 kB 0 B
frontend/dist/WebGPURenderer-B_wkl_Ja 36.3 kB 0 B
frontend/dist/WebScriptsScene 2.78 kB 0 B
frontend/dist/WebVitals 7.41 kB 0 B
frontend/dist/WebVitalsPathBreakdown 3.86 kB 0 B
frontend/dist/webworkerAll-puPV1rBA 397 B 0 B
frontend/dist/wgsl 7.38 kB 0 B
frontend/dist/Wizard 4.7 kB 0 B
frontend/dist/WorkflowScene 117 kB 0 B
frontend/dist/WorkflowsScene 60.1 kB 0 B
frontend/dist/WorldMap 1.04 MB 0 B
frontend/dist/xml 3.02 kB 0 B
frontend/dist/xychartDiagram-2RQKCTM6 39.6 kB 0 B
frontend/dist/yaml 4.64 kB 0 B

compressed-size-action

@TueHaulund TueHaulund force-pushed the tue/replay-vision-lens-validation branch from 79b4022 to ea712bf Compare May 17, 2026 14:26
@TueHaulund TueHaulund marked this pull request as ready for review May 17, 2026 14:27
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 17, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
products/replay_vision/backend/temporal/workflow.py:153-156
**`signals_count` is always 0 — field never populated**

`LensResult(model_output=call_output.model_output)` omits `signals_count`, so it always persists as `0` in the database. The field is documented as "Number of PostHog Signals emitted from this observation", but the workflow has no code that counts or passes a non-zero value. Per simplicity rule 4 ("no superfluous parts"), either the counting logic should be wired in here, or the field (and its `LensResultSerializer` counterpart) should be deferred to a future PR to avoid carrying dead state on every row.

### Issue 2 of 2
products/replay_vision/backend/temporal/activities/call_lens_provider.py:39-45
`_strip_lens_type` mutates the dict it receives rather than working on a copy. `model_json_schema()` returns a fresh dict today, but the mutation is invisible at the call site and would silently corrupt a cached schema if the call convention ever changed. Using `dict(schema)` makes the intent explicit and eliminates the hazard.

```suggestion
def _strip_lens_type(schema: dict[str, Any]) -> dict[str, Any]:
    """Hide the `lens_type` discriminator from the LLM — set server-side via the output class default."""
    schema = dict(schema)
    if "properties" in schema:
        schema["properties"] = {k: v for k, v in schema["properties"].items() if k != "lens_type"}
    if "required" in schema:
        schema["required"] = [r for r in schema["required"] if r != "lens_type"]
    return schema
```

Reviews (1): Last reviewed commit: "feat(replay-vision): API validation + le..." | Re-trigger Greptile

Comment thread products/replay_vision/backend/temporal/workflow.py
Comment thread products/replay_vision/backend/temporal/activities/call_lens_provider.py Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 17, 2026

Reviews (2): Last reviewed commit: "chore(replay-vision): remove remaining d..." | Re-trigger Greptile

@TueHaulund TueHaulund force-pushed the tue/replay-vision-lens-validation branch from 4f86052 to d02bc21 Compare May 17, 2026 15:10
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 17, 2026

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
products/replay_vision/backend/api/lenses.py:178-183
`to_representation` discards the validated `RecordingsQuery` object and then re-calls `.model_validate` only to throw the result away — but the bigger issue is that the check `if data.get("query")` skips the defensive re-validation entirely when the stored query is an empty dict `{}` (falsy). A stored `{}` is a valid query, but if the schema later gains a required field, that row silently skips the guard and is served raw. Using `is not None` avoids the inconsistency.

```suggestion
        if data.get("query") is not None:
            try:
                RecordingsQuery.model_validate(data["query"])
            except PydanticValidationError:
                logger.exception("replay_vision.lens.malformed_query", lens_id=str(instance.id))
                data["query"] = None
```

Reviews (3): Last reviewed commit: "feat(replay-vision): API validation + le..." | Re-trigger Greptile

Validation:
- `lens_config` is validated against the per-`lens_type` Pydantic discriminated
  union (`AnyLens`) in the serializer — a classifier without `tags`, a scorer
  with an inverted scale, or a non-dict config now 400s on save instead of
  failing every observation at runtime.
- `query` is validated against `posthog.schema.RecordingsQuery` (strict,
  `extra="forbid"`) and typed for OpenAPI via `@extend_schema_field`.
  `date_from`/`date_to` are stripped on save — the schedule controls time,
  not the user. The persisted blob is re-validated on read via the lens
  serializer's `to_representation` (log + null on failure).
- `lens_snapshot` and `lens_result` are also validated on read via Pydantic
  at the API serializer boundary (`SerializerMethodField` + `model_validate`),
  with graceful degradation (log + null) on failure.

`lens_result` JSONField on `ReplayObservation`:
- Populated by `mark_observation_succeeded_activity`. Typed via a new
  `LensResult { model_output: AnyLensOutput, signals_count: int }` Pydantic
  model. `AnyLensOutput` is a discriminated union over the five output
  classes; each output class gets `lens_type: Literal[LensType.X]` as the
  discriminator. The literal constraint forces the LLM's structured output
  to emit the correct value.
- Adds `BaseLensOutput.to_event_properties()` for the event flattening
  (excludes `lens_type`, which is already a top-level event property via
  the snapshot).
- API exposes `lens_result` as null until succeeded.

Cleanup: removes the dead `backend/presentation/` bootstrap scaffold + the
`backend/logic/` placeholder; the product is not isolated and doesn't use
the facade pattern.
@TueHaulund TueHaulund force-pushed the tue/replay-vision-lens-validation branch from d02bc21 to 9a003c8 Compare May 17, 2026 15:22
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 17, 2026

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
products/replay_vision/backend/api/lenses.py:169-174
The validated `RecordingsQuery` object is discarded after validation. The stored value is the raw user-supplied dict, which means Pydantic coercions (type normalization, alias resolution, etc.) are never persisted. This is consistent with the explicit intent — "persist exactly what the user sent" — but it also means the read-path re-validation in `to_representation` can encounter values that differ from what a fresh `model_dump` would produce. If you ever add coercing validators to `RecordingsQuery`, the write path and read path will silently diverge. Consider storing the normalized form (`validated.model_dump(mode="json")`) instead, which would make write and read paths equivalent.

```suggestion
        try:
            validated_query = RecordingsQuery.model_validate(attrs["query"])
        except PydanticValidationError as exc:
            raise serializers.ValidationError({"query": str(exc)})
        # Persist the Pydantic-normalized form, minus the date keys the schedule controls.
        attrs["query"] = {
            k: v
            for k, v in validated_query.model_dump(mode="json").items()
            if k not in _QUERY_FIELDS_TO_STRIP
        }
```

Reviews (4): Last reviewed commit: "feat(replay-vision): API validation + le..." | Re-trigger Greptile

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.

1 participant