use crank_registry::{ CreateInvocationLogRequest, InvocationHistoryLossCategory, InvocationHistoryWriteOutcome, ListInvocationLogsQuery, UsageBucket, UsageQuery, }; use sqlx::Row; use super::common::{TestDatabase, test_invocation_log, test_operation, test_workspace_id}; #[tokio::test] async fn invocation_history_write_returns_typed_loss_without_error_details() { let database = TestDatabase::new().await; let registry = database.registry().await; let log = test_invocation_log( "log_missing_owner", &crank_core::OperationId::new("op_missing"), None, crank_core::InvocationStatus::Ok, 10, "2026-03-25T12:20:00Z", ); let outcome = registry .create_invocation_log(CreateInvocationLogRequest { log: &log }) .await; assert_eq!( outcome, InvocationHistoryWriteOutcome::Lost(crank_registry::InvocationHistoryLoss { category: InvocationHistoryLossCategory::InvalidRecord, }) ); database.cleanup().await; } #[tokio::test] #[ignore = "production-size query-plan evidence: inserts 1,000,000 invocation_logs rows"] async fn production_size_invocation_history_queries_stay_bounded() { let database = TestDatabase::new().await; let registry = database.registry().await; let pool = database.raw_pool().await; let operation = test_operation("op_history_scale", 1, crank_core::OperationStatus::Draft); registry .create_operation(&test_workspace_id(), &operation, None) .await .unwrap(); sqlx::query( r#" insert into invocation_logs ( id, workspace_id, operation_id, operation_version, source, level, status, tool_name, message, request_id, trace_id, status_code, duration_ms, error_kind, execution_stage, execution_error_code, retryability, outcome_certainty, request_preview_json, response_preview_json, created_at ) select 'log_scale_' || series::text, 'ws_default', 'op_history_scale', 1, 'admin_test_run', 'info', case when series % 10 = 0 then 'error' else 'ok' end, 'scale_tool', 'scale invocation', '018f0000-0000-7000-8000-' || lpad(series::text, 12, '0'), '0af7651916cd43dd8448eb211c80319c', case when series % 10 = 0 then 500 else 200 end, 20 + (series % 250), null, 'runtime', case when series % 10 = 0 then 'runtime_internal' else null end, 'never', 'certain', '{"input":"bounded"}'::jsonb, '{"ok":true}'::jsonb, '2026-03-25T00:00:00Z'::timestamptz + (series || ' seconds')::interval from generate_series(1, 1000000) as series "#, ) .execute(&pool) .await .unwrap(); let page = registry .list_invocation_logs(ListInvocationLogsQuery { workspace_id: &test_workspace_id(), level: None, status: None, outcome_group: None, search_text: None, source: None, operation_id: Some(&operation.id), agent_id: None, created_after: Some("2026-03-25T00:00:00Z"), created_before: Some("2026-04-06T00:00:00Z"), cursor_created_at: None, cursor_id: None, limit: 101, }) .await .unwrap(); assert_eq!(page.len(), 101); let summary = registry .summarize_usage(UsageQuery { workspace_id: &test_workspace_id(), period: crank_core::UsagePeriod::Last7Days, source: None, created_after: "2026-03-25T00:00:00Z", created_before: "2026-04-06T00:00:00Z", bucket: UsageBucket::Day, }) .await .unwrap(); assert_eq!(summary.rollup.calls_total, 1_000_000); let explain = sqlx::query( r#" explain select id from invocation_logs where workspace_id = 'ws_default' and operation_id = 'op_history_scale' and created_at >= '2026-03-25T00:00:00Z'::timestamptz and created_at < '2026-04-06T00:00:00Z'::timestamptz order by created_at desc, id desc limit 101 "#, ) .fetch_all(&pool) .await .unwrap() .into_iter() .map(|row| row.get::(0)) .collect::>() .join("\n"); assert!( explain.contains("Index Scan") || explain.contains("Bitmap Index Scan"), "{explain}" ); assert!(!explain.contains("Seq Scan"), "{explain}"); database.cleanup().await; }