diff --git a/.gitignore b/.gitignore index 19dcc23..51136e9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ dist/ # Development tools .task/ +.gocache/ +.gomodcache/ # Private documentation docs/VISION.md diff --git a/Taskfile.yml b/Taskfile.yml index 41f179d..e32d8d8 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -56,7 +56,7 @@ tasks: cmds: - go generate ./internal/powersync - go generate ./internal/sqlite - - sed '/^-- Auto-generated/d; /^-- Run .task generate/d' ../control-plane/internal/powersync/schema.sql > internal/chat/tools/query_schema.sql + - sed '/^-- Auto-generated/d; /^-- Run .task generate/d' ../control-plane/internal/infra/powersync/schema.sql > internal/app/chattools/query_schema.sql # =========================================================================== # Build diff --git a/internal/app/app.go b/internal/app/app.go index ba25e5b..189053a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -210,7 +210,6 @@ func (m *Model) newChat() *chat.Model { return chat.New( m.user, m.account, - m.workspace, m.theme, m.db, m.runtimeDeps, diff --git a/internal/app/chat/chat_test.go b/internal/app/chat/chat_test.go index 699a455..8426e42 100644 --- a/internal/app/chat/chat_test.go +++ b/internal/app/chat/chat_test.go @@ -31,7 +31,7 @@ func newTestChat(t *testing.T, client chat.Client) *Model { db := dbtest.OpenTestDB(t) runtimeDeps := usecase.NewRuntimeDeps(db, client) - m := New(nil, domain.Account{ID: "acct-1"}, domain.Workspace{ID: "ws-1"}, theme, db, runtimeDeps, nil, scope) + m := New(nil, domain.Account{ID: "acct-1"}, theme, db, runtimeDeps, nil, scope) m.SetSize(80, 40) return m } diff --git a/internal/app/chat/empty_state_poll_test.go b/internal/app/chat/empty_state_poll_test.go index 4fd1dbb..df3f6cd 100644 --- a/internal/app/chat/empty_state_poll_test.go +++ b/internal/app/chat/empty_state_poll_test.go @@ -30,7 +30,6 @@ func TestEmptyStatePollDoesNotMutateSynchronously(t *testing.T) { m := New( nil, domain.Account{ID: "acct-1"}, - domain.Workspace{ID: "ws-1"}, styles.NewTheme(true), mockDB, usecase.RuntimeDeps{}, @@ -58,7 +57,6 @@ func TestEmptyStateSummaryMessageUpdatesState(t *testing.T) { m := New( nil, domain.Account{ID: "acct-1"}, - domain.Workspace{ID: "ws-1"}, styles.NewTheme(true), mockDB, usecase.RuntimeDeps{}, diff --git a/internal/app/chat/input_flow.go b/internal/app/chat/input_flow.go index c28eec9..d0371ee 100644 --- a/internal/app/chat/input_flow.go +++ b/internal/app/chat/input_flow.go @@ -37,7 +37,6 @@ func (m *Model) createConversation(input msgs.UserSubmittedInput) tea.Cmd { convID, err := m.db.Conversations().Create( ctx, m.account.ID, - m.workspace.ID, ) if err != nil { m.scope.Error("failed to create conversation", "error", err) diff --git a/internal/app/chat/model.go b/internal/app/chat/model.go index d3965c9..60b9750 100644 --- a/internal/app/chat/model.go +++ b/internal/app/chat/model.go @@ -57,14 +57,13 @@ type Model struct { conversationID domain.ConversationID session *corechat.Session - user *auth.User - account domain.Account - workspace domain.Workspace - theme styles.Theme - width int - height int - originX int - originY int + user *auth.User + account domain.Account + theme styles.Theme + width int + height int + originX int + originY int // Empty state policySummary *domain.AccountSummary @@ -88,7 +87,6 @@ type emptyStateSummaryLoadedMsg struct { func New( user *auth.User, account domain.Account, - workspace domain.Workspace, theme styles.Theme, db sqlite.DB, runtimeDeps usecase.RuntimeDeps, @@ -103,7 +101,6 @@ func New( messageList: messagelist.New(theme, runtimeDeps, toolRegistry, scope), user: user, account: account, - workspace: workspace, theme: theme, db: db, runtimeDeps: runtimeDeps, diff --git a/internal/app/chattools/query_schema.sql b/internal/app/chattools/query_schema.sql index 65b13e4..f2c7a18 100644 --- a/internal/app/chattools/query_schema.sql +++ b/internal/app/chattools/query_schema.sql @@ -11,71 +11,58 @@ -- -- All queries are READ-ONLY. This is a local sync of server data. --- Live working set of entities (services, log events) referenced in a conversation -CREATE TABLE conversation_contexts ( - id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from conversation.account_id. - added_by TEXT, -- Who added this entity to context. user: added via @-reference, assistant: added by AI during chat. - conversation_id TEXT, -- Conversation this context belongs to - created_at TEXT, -- When the entity was added to context - entity_id TEXT, -- ID of the context entity - entity_type TEXT -- Type of the context entity. service: an application producing logs, log_event: a specific event pattern. -); - --- Chat session between a user and the AI assistant within a workspace +-- Chat session between a user and the AI assistant within an account. CREATE TABLE conversations ( id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. + account_id TEXT, -- Account this conversation belongs to created_at TEXT, -- When the conversation was created title TEXT, -- AI-generated title, set after first exchange - user_id TEXT, -- WorkOS user ID who owns this conversation - view_id TEXT, -- If set, this conversation is for iterating on a specific view - workspace_id TEXT -- Workspace this conversation belongs to + user_id TEXT -- WorkOS user ID who owns this conversation ); --- Cache table for datadog_account_statuses view. Refreshed by cron service. +-- Cache table for canonical datadog_account_statuses view. Refreshed by worker-owned status loops. CREATE TABLE datadog_account_statuses_cache ( id TEXT, - account_id TEXT, -- Account ID (denormalized from Datadog account) - datadog_account_id TEXT, -- The Datadog account this status belongs to - disabled_services INTEGER, -- Services with DISABLED health - estimated_bytes_reduction_per_hour REAL, -- Account-wide estimated bytes reduction - estimated_cost_reduction_per_hour_bytes_usd REAL, -- Account-wide estimated bytes-based USD/hour savings - estimated_cost_reduction_per_hour_usd REAL, -- Account-wide estimated total USD/hour savings - estimated_cost_reduction_per_hour_volume_usd REAL, -- Account-wide estimated volume-based USD/hour savings - estimated_volume_reduction_per_hour REAL, -- Account-wide estimated volume reduction - health TEXT, -- Overall health of the Datadog account. DISABLED (integration turned off), INACTIVE (no data received), OK (healthy). - inactive_services INTEGER, -- Services with INACTIVE health - log_active_services INTEGER, -- Services not DISABLED or INACTIVE - log_event_analyzed_count INTEGER, -- Number of log events that have been analyzed - log_event_bytes_per_hour REAL, -- Discovered log event throughput in bytes/hour across all services - log_event_cost_per_hour_bytes_usd REAL, -- Discovered log event ingestion cost in USD/hour across all services - log_event_cost_per_hour_usd REAL, -- Discovered log event total cost in USD/hour across all services - log_event_cost_per_hour_volume_usd REAL, -- Discovered log event indexing cost in USD/hour across all services - log_event_count INTEGER, -- Total log events across all services - log_event_volume_per_hour REAL, -- Discovered log event throughput in events/hour across all services - log_service_count INTEGER, -- Total number of services - observed_bytes_per_hour_after REAL, -- Account-wide observed current bytes - observed_bytes_per_hour_before REAL, -- Account-wide observed pre-approval bytes - observed_cost_per_hour_after_bytes_usd REAL, -- Account-wide measured bytes-based USD/hour cost after approval - observed_cost_per_hour_after_usd REAL, -- Account-wide measured total USD/hour cost after approval - observed_cost_per_hour_after_volume_usd REAL, -- Account-wide measured volume-based USD/hour cost after approval - observed_cost_per_hour_before_bytes_usd REAL, -- Account-wide measured bytes-based USD/hour cost before approval - observed_cost_per_hour_before_usd REAL, -- Account-wide measured total USD/hour cost before approval - observed_cost_per_hour_before_volume_usd REAL, -- Account-wide measured volume-based USD/hour cost before approval - observed_volume_per_hour_after REAL, -- Account-wide observed current volume - observed_volume_per_hour_before REAL, -- Account-wide observed pre-approval volume - ok_services INTEGER, -- Services with OK health - policy_approved_count INTEGER, -- Policies approved by user - policy_dismissed_count INTEGER, -- Policies dismissed by user - policy_pending_count INTEGER, -- Policies awaiting user action - policy_pending_critical_count INTEGER, -- Pending policies with critical compliance severity - policy_pending_high_count INTEGER, -- Pending policies with high compliance severity - policy_pending_low_count INTEGER, -- Pending policies with low compliance severity - policy_pending_medium_count INTEGER, -- Pending policies with medium compliance severity - ready_for_use INTEGER, -- True when at least 1 log event has been analyzed - service_cost_per_hour_volume_usd REAL, -- Service-level indexing cost in USD/hour across all services - service_volume_per_hour REAL -- Ground-truth throughput in events/hour from service_log_volumes across all services + account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_service_events_per_hour REAL, + current_service_volume_usd_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + datadog_account_id TEXT, + disabled_services INTEGER, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_log_event_count INTEGER, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, + health TEXT, -- Health state for this status row. Values: DISABLED = The user turned the log pipeline off.; INACTIVE = The service currently has no log volume.; ERROR = The log pipeline is unhealthy or failing.; OK = The log pipeline is healthy. + inactive_services INTEGER, + log_active_services INTEGER, + log_event_analyzed_count INTEGER, + log_event_count INTEGER, + log_service_count INTEGER, + ok_services INTEGER, + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_log_event_count INTEGER, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + ready_for_use INTEGER ); -- Datadog integration configuration for an account, one per account @@ -85,7 +72,7 @@ CREATE TABLE datadog_accounts ( cost_per_gb_ingested REAL, -- Cost per GB of log data ingested (USD). NULL = using Datadog's published rate ($0.10/GB). Set to override with actual contract rate. created_at TEXT, -- When the Datadog account was created name TEXT, -- Display name for this Datadog account - site TEXT -- Datadog regional site. US1: datadoghq.com, US3: us3.datadoghq.com, US5: us5.datadoghq.com, EU1: datadoghq.eu, US1_FED: ddog-gov.com, AP1: ap1.datadoghq.com, AP2: ap2.datadoghq.com. + site TEXT -- Datadog regional site. Values: US1 = datadoghq.com.; US3 = us3.datadoghq.com.; US5 = us5.datadoghq.com.; EU1 = datadoghq.eu.; US1_FED = ddog-gov.com.; AP1 = ap1.datadoghq.com.; AP2 = ap2.datadoghq.com. ); -- Discovered Datadog log index where logs are stored (e.g., main, security, compliance) @@ -98,170 +85,339 @@ CREATE TABLE datadog_log_indexes ( name TEXT -- Index name from Datadog (e.g., 'main', 'security', 'compliance') - this is the stable identifier ); --- Ground truth record for a field in a log event. Accumulates metadata as more production data is observed. -CREATE TABLE log_event_fields ( +-- Current AI curation for a finding. +CREATE TABLE finding_curations ( id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. - baseline_avg_bytes REAL, -- Current trailing 7-day volume-weighted average bytes for this attribute. Refreshed on volume ingestion. - created_at TEXT, -- When this field was first discovered - field_path TEXT, -- Unambiguous path segments, e.g. {attributes, http, status} - log_event_id TEXT, -- The log event this field belongs to - -- Top-N observed values with proportions. Populated on-demand for fields that need faceting (e.g., user agents for bot detection). - -- Opaque JSON data. Query using SQLite json_extract() or json_each(). - value_distribution TEXT + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from finding.account_id. + body TEXT, -- User-facing markdown-friendly explanation of the curated finding. + created_at TEXT, -- When this curation row was created. + disposition TEXT, -- Whether this candidate should be kept as a legitimate finding or suppressed. Values: keep = Keep the finding as a legitimate user-facing issue.; suppress = Do not keep this candidate as a user-facing finding. + finding_id TEXT, -- The finding this curation belongs to + finding_problem_version INTEGER, -- Problem version of the finding when this curation was last produced. + priority TEXT, -- How much attention a kept finding deserves. Values: low = Legitimate finding, but low urgency or prominence.; medium = Legitimate finding with clear but not top-tier urgency.; high = Legitimate finding that deserves strong user attention. + title TEXT, -- Short user-facing title for the curated finding. + version INTEGER -- Version of the curation contract and prompt that produced this record. ); --- AI-generated recommendation for a specific quality category on a log event, scoped to a workspace for approval -CREATE TABLE log_event_policies ( +-- Explicit membership between a finding and a log event. +CREATE TABLE finding_log_events ( id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. - action TEXT, -- What this policy does when enforced: 'drop' (remove all events), 'sample' (keep at reduced rate), 'filter' (drop subset by field value), 'trim' (remove/truncate fields), 'none' (informational only). Auto-set via trigger. - -- Category-specific analysis from AI. JSON object with one field populated matching the category, containing the analysis and recommended actions. - -- JSON object. Fields: - -- $.pii_leakage - PII leakage analysis (optional) - -- $.pii_leakage.fields[] - List of fields that may contain PII - -- $.pii_leakage.fields[].path[] string[] - Path to field as array of segments - -- $.pii_leakage.fields[].types[] string[] - List of sensitive data types this field may contain - -- $.pii_leakage.fields[].observed boolean - Whether actual sensitive data was seen in examples - -- $.secrets_leakage - Secrets leakage analysis (optional) - -- $.secrets_leakage.fields[] - List of fields that may contain secrets - -- $.phi_leakage - PHI leakage analysis (optional) - -- $.phi_leakage.fields[] - List of fields that may contain PHI - -- $.payment_data_leakage - Payment data leakage analysis (optional) - -- $.payment_data_leakage.fields[] - List of fields that may contain payment data - -- $.health_checks - Health checks analysis (optional) - -- $.bot_traffic - Bot traffic analysis (optional) - -- $.bot_traffic.user_agent_field[] string[] - Path to user-agent field as array of segments - -- $.bot_traffic.bot_proportion number - Fraction of traffic identified as bot/crawler (optional) - -- $.debug_artifacts - Debug artifacts analysis (optional) - -- $.malformed - Malformed data analysis (optional) - -- $.broken_records - Broken records analysis (optional) - -- $.broken_records.min_interval_seconds number - Suggested minimum interval between kept events in seconds - -- $.commodity_traffic - Commodity traffic analysis (optional) - -- $.commodity_traffic.min_interval_seconds number - Suggested minimum interval between kept events in seconds - -- $.redundant_events - Redundant events analysis (optional) - -- $.dead_weight - Dead weight analysis (optional) - -- $.duplicate_fields - Duplicate fields analysis (optional) - -- $.duplicate_fields.pairs[] - List of duplicate field pairs - -- $.duplicate_fields.pairs[].remove[] string[][] - List of duplicate field paths to remove - -- $.duplicate_fields.pairs[].keep[] string[] - Canonical field path to keep - -- $.instrumentation_bloat - Instrumentation bloat analysis (optional) - -- $.instrumentation_bloat.fields[] string[][] - List of field paths that are instrumentation bloat - -- $.oversized_fields - Oversized fields analysis (optional) - -- $.oversized_fields.fields[] string[][] - List of field paths that are oversized - -- $.wrong_level - Wrong level analysis (optional) - -- $.wrong_level.current_level string - Current normalized severity level - -- $.wrong_level.suggested_level string - Suggested normalized severity level - -- - -- Example: json_extract(analysis, '$.field_name') - analysis TEXT, - approved_at TEXT, -- When this policy was approved by a user - approved_baseline_avg_bytes REAL, -- Baseline avg bytes frozen at approval time. Snapshot of log_event.baseline_avg_bytes. - approved_baseline_volume_per_hour REAL, -- Baseline volume/hour frozen at approval time. Snapshot of log_event.baseline_volume_per_hour. - approved_by TEXT, -- User ID who approved this policy - category TEXT, -- Quality issue category this policy addresses. Compliance: pii_leakage, secrets_leakage, phi_leakage, payment_data_leakage. Waste: health_checks, bot_traffic, debug_artifacts, malformed, broken_records, commodity_traffic, redundant_events, dead_weight. Quality: duplicate_fields, instrumentation_bloat, oversized_fields, wrong_level. - category_type TEXT, -- Type of problem: compliance (legal/security risk), waste (event-level cuts), or quality (field-level improvements). Auto-set via trigger from CategoryMeta. - created_at TEXT, -- When this policy was created - dismissed_at TEXT, -- When this policy was dismissed by a user - dismissed_by TEXT, -- User ID who dismissed this policy - log_event_id TEXT, -- The log event this policy applies to - severity TEXT, -- Max compliance severity across sensitivity types. NULL for non-compliance categories. Auto-set via trigger. Values: low, medium, high, critical. - subjective INTEGER, -- Whether this category requires AI judgment (true) vs mechanically verifiable (false). Auto-set via trigger from CategoryMeta. - workspace_id TEXT -- The workspace that owns this policy + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from finding.account_id. + created_at TEXT, -- When this membership row was created. + finding_id TEXT, -- The finding this membership belongs to + log_event_id TEXT -- The log event associated with this finding ); --- Cache table for per-category policy aggregations. Refreshed by cron service. -CREATE TABLE log_event_policy_category_statuses_cache ( - id TEXT, - account_id TEXT, -- Account ID for tenant isolation - action TEXT, -- What the policy does: drop (remove events), sample (reduce rate), filter (drop subset), trim (modify fields), none (informational) - approved_count INTEGER, -- Policies approved by user in this category - boundary TEXT, -- Where this category stops applying — what NOT to flag - category TEXT, -- Quality issue category (e.g., pii_leakage, noise, health_checks) - category_type TEXT, -- Type of problem: compliance (legal/security risk), waste (event-level cuts), quality (field-level improvements). - dismissed_count INTEGER, -- Policies dismissed by user in this category - display_name TEXT, -- Human-readable category name (e.g., 'PII Leakage') - estimated_bytes_reduction_per_hour REAL, -- Bytes/hour saved by all pending policies in this category combined - estimated_cost_reduction_per_hour_bytes_usd REAL, -- Estimated ingestion savings in USD/hour from pending policies in this category - estimated_cost_reduction_per_hour_usd REAL, -- Estimated total savings in USD/hour from pending policies in this category - estimated_cost_reduction_per_hour_volume_usd REAL, -- Estimated indexing savings in USD/hour from pending policies in this category - estimated_volume_reduction_per_hour REAL, -- Events/hour saved by all pending policies in this category combined - events_with_volumes INTEGER, -- Log events in this category that have volume data (subset of total_event_count) - pending_count INTEGER, -- Policies awaiting user review in this category - policy_pending_critical_count INTEGER, -- Pending policies with critical compliance severity - policy_pending_high_count INTEGER, -- Pending policies with high compliance severity - policy_pending_low_count INTEGER, -- Pending policies with low compliance severity - policy_pending_medium_count INTEGER, -- Pending policies with medium compliance severity - principle TEXT, -- What this category detects — the fundamental test for membership - subjective INTEGER, -- Whether this category requires AI judgment (true) vs mechanically verifiable (false) - total_event_count INTEGER -- Total log events that have a policy in this category +-- Versioned working remediation plans for findings. +CREATE TABLE finding_plans ( + id TEXT, -- Unique identifier + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from finding.account_id. + created_at TEXT, -- When this plan row was created. + finding_curation_id TEXT, -- The curation that produced this plan revision + finding_id TEXT, -- The finding this plan belongs to + -- Open questions that materially affect whether this plan should be approved or revised. + -- JSON array of objects. Each element: + -- $[0].scope string - Scope where the missing input applies, such as service, environment, or rollout. + -- $[0].key string - Stable machine-readable key for the question. + -- $[0].question string - User-facing question that needs to be answered. + -- $[0].why_it_matters string - Why the answer materially affects whether the plan should be approved or revised. + -- $[0].choices[] string[] - Suggested answer choices when the question is constrained. + -- $[0].default_answer string - Suggested default answer when one exists. + -- + -- Example: json_extract(open_questions, '$[0].field_name') + open_questions TEXT, + rationale TEXT, -- Why this plan is the preferred remediation path. + revision INTEGER, -- Monotonic revision number for this finding's plan. + status TEXT, -- Current lifecycle state for this plan revision. Values: draft = Initial working draft.; needs_input = Waiting on missing context or operator input.; ready_for_review = Ready for human review.; approved = Approved for execution.; executing = Execution is in progress.; completed = Execution finished successfully.; failed = Execution or validation failed.; superseded = Replaced by a newer revision.; canceled = Canceled before completion. + -- Ordered generic remediation steps expressed as intent, not provider-specific implementation. + -- JSON array of objects. Each element: + -- $[0].kind string - Step discriminator identifying the remediation action kind. + -- $[0].summary string - Short summary of what the remediation step does. + -- $[0].payload object - Step-specific structured payload when the step kind needs extra configuration. + -- + -- Example: json_extract(steps, '$[0].field_name') + steps TEXT, + summary TEXT, -- Summary of the remediation plan. + title TEXT, -- Short user-facing title for this plan. + version INTEGER -- Version of the plan contract and prompt that produced this record. ); --- Cache table for log_event_policy_statuses view. Refreshed by cron service. -CREATE TABLE log_event_policy_statuses_cache ( +-- Cache table for canonical finding_statuses view. Refreshed by worker-owned status loops. +CREATE TABLE finding_statuses_cache ( id TEXT, - account_id TEXT, -- Account ID for tenant isolation - action TEXT, -- What the policy does: drop (remove events), sample (reduce rate), filter (drop subset), trim (modify fields), none (informational) - approved_at TEXT, -- When this policy was approved by a user - bytes_per_hour REAL, -- Current throughput of the targeted log event in bytes/hour - category TEXT, -- Quality issue category this policy addresses (e.g., pii_leakage, noise, health_checks) - category_type TEXT, -- Type of problem: compliance (legal/security risk), waste (event-level cuts), quality (field-level improvements). - created_at TEXT, -- When this policy was created - dismissed_at TEXT, -- When this policy was dismissed by a user - estimated_bytes_reduction_per_hour REAL, -- Bytes/hour saved if this policy applied alone. NULL if not estimable. - estimated_cost_reduction_per_hour_bytes_usd REAL, -- Estimated ingestion savings in USD/hour from bytes reduction - estimated_cost_reduction_per_hour_usd REAL, -- Estimated total savings in USD/hour (bytes + volume) - estimated_cost_reduction_per_hour_volume_usd REAL, -- Estimated indexing savings in USD/hour from volume reduction - estimated_volume_reduction_per_hour REAL, -- Events/hour saved if this policy applied alone. NULL if not estimable. - log_event_id TEXT, -- The log event this policy targets - log_event_name TEXT, -- Name of the targeted log event (denormalized for display) - policy_id TEXT, -- The policy this status row represents - service_id TEXT, -- Service that produces the targeted log event (denormalized) - service_name TEXT, -- Name of the service (denormalized for display) - severity TEXT, -- Max compliance severity across sensitivity types. NULL for non-compliance categories. Values: low, medium, high, critical. - status TEXT, -- User decision on this policy. PENDING (awaiting review), APPROVED (accepted for enforcement), DISMISSED (rejected by user). - subjective INTEGER, -- Whether this category requires AI judgment (true) vs mechanically verifiable (false) - survival_rate REAL, -- Fraction of events that survive this policy (0.0 = all dropped, 1.0 = all kept). NULL if not estimable. - volume_per_hour REAL, -- Current throughput of the targeted log event in events/hour - workspace_id TEXT -- The workspace that owns this policy + account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + finding_created_at TEXT, + finding_id TEXT, + finding_updated_at TEXT, + isolated_bytes_per_hour REAL, + isolated_bytes_usd_per_hour REAL, + isolated_events_per_hour REAL, + isolated_saved_bytes_per_hour REAL, + isolated_saved_bytes_usd_per_hour REAL, + isolated_saved_events_per_hour REAL, + isolated_saved_total_usd_per_hour REAL, + isolated_saved_volume_usd_per_hour REAL, + isolated_total_usd_per_hour REAL, + isolated_volume_usd_per_hour REAL, + log_event_count INTEGER, + log_event_id TEXT, + plan_status TEXT, + plan_updated_at TEXT, + scope_kind TEXT, -- Explicit scope discriminator for this finding. Values: account, service, log_event. + service_id TEXT ); --- Cache table for log_event_statuses view. Refreshed by cron service. +-- Durable issue instance produced by a problem over catalog facts and telemetry baselines. +CREATE TABLE findings ( + id TEXT, -- Unique identifier + account_id TEXT, -- Parent account this finding belongs to + closed_at TEXT, -- When this finding stopped matching the current world. Null while active. + created_at TEXT, -- When this finding row was created. + -- Problem-specific typed details for this finding instance. + -- JSON shape depends on type. + -- + -- Variant: background_noise + -- Fields: + -- $.peer_label string - Named peer label involved in the noisy interaction when one is known. + -- $.peer_role string - Coarse role of the peer involved in the noisy interaction. + -- $.peer_kind string - Coarse kind of peer involved in the noisy interaction when one is known. + -- + -- Variant: commodity_traffic + -- Fields: + -- $.event_name string - Log event name identified as commodity traffic. + -- + -- Variant: dead_weight + -- Fields: + -- $.event_name string - Log event name identified as dead weight. + -- + -- Variant: debug_artifacts + -- Fields: + -- $.event_name string - Log event name identified as a debug artifact. + -- + -- Variant: debug_marker + -- Fields: + -- $.event_name string - Log event name identified as a debug marker. + -- + -- Variant: hot_path + -- Fields: + -- $.emission_template string - Stable emission template associated with the hot path event. + -- + -- Variant: level_too_high + -- Fields: + -- $.event_name string - Log event name whose level is higher than expected. + -- $.current_level string - Observed log level on the event. + -- $.expected_level string - Expected log level for the event. + -- + -- Variant: level_too_low + -- Fields: + -- $.event_name string - Log event name whose level is lower than expected. + -- $.current_level string - Observed log level on the event. + -- $.expected_level string - Expected log level for the event. + -- + -- Variant: reactive_flood + -- Fields: + -- $.operation string - Stable operation associated with the reactive flood behavior. + -- $.outcome string - Durable outcome associated with the reactive flood behavior. + -- $.peer_label string - Named peer label involved in the reactive interaction when one is known. + -- + -- Variant: routine_system_chatter + -- Fields: + -- $.event_name string - Log event name identified as routine system chatter. + -- $.expected_level string - Expected log level for this routine chatter event. + -- $.operator_prominence string - Operator prominence judgment associated with the event. + -- $.instance_value string - Per-instance value judgment associated with the event. + -- $.collection_gain string - Collection-level gain judgment associated with the event. + -- + -- Variant: sensitive_data_exposure + -- Fields: + -- $.event_name string - Log event name where the exposure was observed. + -- $.exposure_classes string - Comma-separated list of exposure classes observed in the event. + -- $.exposure_class_count number - Number of distinct exposure classes observed in the event. + -- $.highest_risk_class string - Highest-risk exposure class observed in the event. + -- $.has_secret_exposure boolean - Whether secret material was exposed. + -- $.has_payment_exposure boolean - Whether payment-related data was exposed. + -- $.has_pii_exposure boolean - Whether personally identifiable information was exposed. + -- $.has_phi_exposure boolean - Whether protected health information was exposed. + -- $.secret_paths string - Comma-separated event paths where secret material was exposed. + -- $.payment_paths string - Comma-separated event paths where payment-related data was exposed. + -- $.pii_paths string - Comma-separated event paths where personally identifiable information was exposed. + -- $.phi_paths string - Comma-separated event paths where protected health information was exposed. + -- + -- Example: json_extract(details, '$.field_name') with the matching discriminator columns. + details TEXT, + domain TEXT, -- Top-level product domain this finding belongs to, e.g. quality or operations. + fingerprint TEXT, -- Stable problem-specific identity for this finding instance within an account. + log_event_id TEXT, -- Associated log event when this finding is explicitly scoped to one log event. + problem_version INTEGER, -- Version of the problem logic that most recently reconciled this finding. + scope_kind TEXT, -- Explicit scope discriminator for this finding. Values: account, service, log_event. + service_id TEXT, -- Owning service for service-scoped or log-event-scoped findings. Null only for account-scoped findings. + type TEXT -- Problem type that raised this finding, e.g. reactive_flood. +); + +-- Extensible typed facts attached to a log event. +CREATE TABLE log_event_facts ( + id TEXT, -- Unique identifier + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. + created_at TEXT, -- When this fact was first recorded + fact_name TEXT, -- Fact name within the slice, e.g. identity_profile, operational_role, value_profile. + log_event_id TEXT, -- The log event this fact belongs to + slice_name TEXT, -- Owned fact slice refreshed together, e.g. identity, meaning, quality. + slice_version INTEGER, -- Current code version applied for this slice. + -- Typed fact payload stored as JSONB. Shape is determined by slice_name + fact_name. + -- JSON shape depends on slice_name + fact_name. + -- + -- Variant: identity + identity_profile + -- Captures the intrinsic event identity: what happened, what durable thing it is about, the durable outcome if one exists, and the stable operation when that matters. + -- Fields: + -- $.action string - The coarse stable thing that happened. + -- $.subject_class string - The coarse stable class the subject belongs to. + -- $.subject string - What specific durable thing the event is fundamentally about. + -- $.outcome string - The durable outcome, when one exists. + -- $.operation string - The stable named operation, when one exists. + -- + -- Variant: attribution + attribution_profile + -- Captures the separate materially involved peer boundary around the event when one is clearly exposed. + -- Fields: + -- $.peer_label string - The most specific stable peer identity materially involved, if one is clear. + -- $.peer_role string - The coarse operational role of the materially involved peer. Values: user=A human or end-user actor is the materially involved peer.; service=Another application or worker service is the materially involved peer.; dependency=A supporting dependency such as a database, cache, queue, storage system, or external API is the materially involved peer.; bot=A crawler or bot actor is the materially involved peer.; probe=A health, readiness, uptime, or probe actor is the materially involved peer.; unknown=A peer is involved but the broad role is still unknown.. + -- $.peer_kind string - The operational kind of the materially involved peer. Values: database=A database peer such as Postgres or MySQL.; cache=A cache peer such as Redis or Memcached.; queue=A queue or message-bus peer such as Kafka, SQS, or EventBridge.; object_store=An object storage peer such as S3 or GCS.; internal_api=A first-party internal API peer.; external_api=A third-party or otherwise external API peer.; payment_service=A payment-processing service peer such as Stripe.; email_service=An email-delivery service peer such as SES or Mailchimp.; unknown=A peer is involved but the operational kind is still unknown.. + -- $.boundary_side string - The coarse side of the materially involved peer boundary. Values: internal=The materially involved peer is another internal system or boundary within the same estate.; upstream=The materially involved peer appears to be upstream of the current service or is initiating work into it.; downstream=The materially involved peer appears to be downstream of the current service or is being called by it.. + -- + -- Variant: structural + structure_profile + -- Captures the event-facing structural summary from log stream structure, including real stream and execution keys. + -- Fields: + -- $.body_kind string - The deterministic body usefulness class. Values: empty=The body is empty.; generic=The body is generic and weakly descriptive.; opaque=The body is opaque or machine-encoded.; descriptive=The body is semantically descriptive.. + -- $.is_fragment boolean - Whether the event is a fragment rather than a complete semantic event. + -- $.template string - The stable message or structural template when one is visible. + -- $.stream_correlation_keys[] string[] - The canonical stream correlation keys observed in structure analysis. + -- $.execution_correlation_keys[] string[] - The canonical execution or work-unit correlation keys observed in structure analysis. + -- $.gaps[] string[] - Meaningful structural gaps observed during structure analysis. Values: empty_capture=No usable records were available for structure analysis.; missing_stream_partitions=Expected stream partitioning was not visible.; missing_execution_grouping=Expected execution grouping was not visible.. + -- + -- Variant: artifact + event_role_profile + -- Classifies the event's coarse observability artifact role as signal, marker, debug artifact, or garbled output. + -- Fields: + -- $.role string - The event's coarse observability role. Values: signal=A readable event, state change, alert, or outcome artifact.; marker=A breadcrumb, checkpoint, or lightweight progress artifact.; debug_artifact=A debug-useful payload artifact such as a request, response, dump, or snapshot emitted inline into logs.; garbled=A malformed or unintelligible artifact.. + -- + -- Variant: artifact + emission_template_profile + -- Captures the stable emitted wording this event follows as an instrumentation artifact. + -- Fields: + -- $.template string - The stable emitted wording for this event, with descriptive placeholders for clearly variable slots. + -- + -- Variant: quality + operator_prominence_profile + -- Captures how prominently the event deserves to appear in a normal operator workflow. + -- Fields: + -- $.operator_prominence string - How prominently this event deserves to appear in normal operator workflows. Values: none=Implementation-step detail or housekeeping chatter that does not deserve routine operator prominence.; low=Situationally useful proof-of-life, maintenance summary, or routine lifecycle context.; high=Important operator-facing signal about health, failures, coordination, state, or meaningful lifecycle changes.. + -- + -- Variant: quality + observability_value_profile + -- Captures how much one representative event matters on its own and how much extra value the larger collection adds beyond one representative copy. + -- Fields: + -- $.instance_value string - How helpful one representative event instance is on its own. Values: none=One representative instance adds essentially no meaningful observability clue.; low=One representative instance adds a weak but real observability clue.; high=One representative instance is directly valuable on its own.. + -- $.collection_gain string - How much additional observability value the larger collection adds beyond one representative instance. Values: none=The larger collection adds essentially no extra value beyond one representative instance.; low=The larger collection adds limited but real extra value beyond one representative instance.; high=The larger collection adds materially important value beyond one representative instance.. + -- + -- Variant: quality + level_expectation_profile + -- Captures the normalized severity level the event deserves. + -- Fields: + -- $.expected_level string - The severity level this event should use. Values: debug=Implementation-facing inspection, trace, planning, or evaluation detail.; info=Expected operator-facing work, lifecycle, housekeeping, or state-change signal.; warn=Degraded, unexpected, retrying, or risky conditions where the system is still operating and the intended work has not clearly failed outright.; error=A concrete failure or hard broken condition where the intended work did not complete.. + -- + -- Variant: quality + payload_hygiene_profile + -- Captures where event-local payload weight lives across the body and event attributes, plus the exact costly paths. + -- Fields: + -- $.body_weight string - The weight carried by the log body itself. Values: lean=The body is compact and inexpensive.; moderate=The body is moderate in size and cost.; heavy=The body is heavy and materially costlier than normal.. + -- $.attribute_weight string - The weight carried by event attributes. Values: lean=The event attributes are compact and inexpensive.; moderate=The event attributes are moderate in size and cost.; heavy=The event attributes are heavy and materially costlier than normal.. + -- $.expensive_paths[] string[][] - Concrete paths that materially increase cost or clutter. + -- + -- Variant: quality + context_completeness_profile + -- Captures whether the event carries the trace and work-unit identifiers that should exist on this event, and names any concrete required paths that are missing. + -- Fields: + -- $.trace_context string - Whether the event carries the distributed trace identifiers it should have. Values: complete=Trace context is complete.; partial=Trace context is partially present.; missing=Trace context is missing.; not_applicable=Trace context does not belong on this event shape.. + -- $.work_unit_context string - Whether the event carries the concrete request, job, workflow, batch, message, or similar execution identifier it should have. Values: complete=Work-unit context is complete.; partial=Work-unit context is partially present.; missing=Work-unit context is missing.; not_applicable=Work-unit context does not belong on this event shape.. + -- $.missing_required_paths[] string[][] - Concrete correlation paths that should exist on this event but are missing. + -- + -- Variant: compliance + sensitive_data_profile + -- Lists the exact event paths that actually expose secrets, payment data, PII, or PHI in sampled evidence. + -- Fields: + -- $.secret_paths[] string[][] - Exact paths that expose secrets or credentials. + -- $.payment_paths[] string[][] - Exact paths that expose payment data. + -- $.pii_paths[] string[][] - Exact paths that expose actionable personally identifying data. + -- $.phi_paths[] string[][] - Exact paths that expose protected health information. + -- + -- Example: json_extract(value, '$.field_name') with the matching discriminator columns. + value TEXT +); + +-- Per-log-event stream-change expressions produced from findings and merged into preview/effective states. +CREATE TABLE log_event_policies ( + id TEXT, -- Unique identifier + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. + compiled_at TEXT, -- When this policy row was last compiled from its current inputs. + created_at TEXT, -- When this policy row was created. + finding_id TEXT, -- Owning finding for contribution rows. Null for merged preview/effective rows. + kind TEXT, -- Policy role for this log event. Values: contribution = One finding's contribution row.; preview = Merged preview state before enforcement.; effective = Merged enforced state currently in effect. + log_event_id TEXT, -- The log event this policy applies to + -- Typed compiled per-log-event stream change. Shared by contribution, preview, and effective rows. + -- JSON object. Fields: + -- $.drop object - Drop operation applied to the event stream. + -- $.drop.enabled boolean - Whether matching events should be dropped entirely. + -- $.sample object - Sampling operation applied to the event stream. + -- $.sample.enabled boolean - Whether matching events should be sampled. + -- $.sample.interval_seconds number - Sampling interval in seconds when sampling is enabled. + -- $.trim object - Trim operation that removes expensive fields from the event payload. + -- $.trim.enabled boolean - Whether matching events should have fields trimmed. + -- $.trim.target_paths[] string[][] - Event field paths that should be removed from the payload. + -- $.redact object - Redaction operations applied to sensitive fields in the event payload. + -- $.redact.entries[] object[] - Redaction entries applied to the payload. + -- $.redact.entries[].target_paths[] string[][] - Event field paths that should be redacted. + -- $.rewrite object - Rewrite operations that change severity or move fields within the event payload. + -- $.rewrite.severity object - Severity rewrite applied to the event. + -- $.rewrite.severity.value string - New severity value assigned to the event. + -- $.rewrite.fields[] object[] - Field move rewrites applied to the event payload. + -- $.rewrite.fields[].from[] string[] - Source field path in the event payload. + -- $.rewrite.fields[].to[] string[] - Destination field path in the event payload. + -- + -- Example: json_extract(spec, '$.field_name') + spec TEXT +); + +-- Cache table for canonical log_event_statuses view. Refreshed by worker-owned status loops. CREATE TABLE log_event_statuses_cache ( id TEXT, - account_id TEXT, -- Account ID for tenant isolation - approved_policy_count INTEGER, -- Policies approved by user - bytes_per_hour REAL, -- Current throughput in bytes/hour (rolling 7-day) - cost_per_hour_bytes_usd REAL, -- Current ingestion cost in USD/hour - cost_per_hour_usd REAL, -- Current total cost in USD/hour (bytes + volume) - cost_per_hour_volume_usd REAL, -- Current indexing cost in USD/hour - dismissed_policy_count INTEGER, -- Policies dismissed by user - estimated_bytes_reduction_per_hour REAL, -- Bytes/hour saved by all policies combined - estimated_cost_reduction_per_hour_bytes_usd REAL, -- Estimated ingestion savings in USD/hour - estimated_cost_reduction_per_hour_usd REAL, -- Estimated total savings in USD/hour (bytes + volume) - estimated_cost_reduction_per_hour_volume_usd REAL, -- Estimated indexing savings in USD/hour - estimated_volume_reduction_per_hour REAL, -- Events/hour saved by all policies combined - has_been_analyzed INTEGER, -- Whether AI has analyzed this log event - has_volumes INTEGER, -- Whether volume data exists for this log event - log_event_id TEXT, -- The log event this status belongs to - observed_bytes_per_hour_after REAL, -- Measured bytes/hour after policy approval (current) - observed_bytes_per_hour_before REAL, -- Measured bytes/hour before first policy approval - observed_cost_per_hour_after_bytes_usd REAL, -- Measured ingestion cost after approval (current) - observed_cost_per_hour_after_usd REAL, -- Measured total cost after approval (current) - observed_cost_per_hour_after_volume_usd REAL, -- Measured indexing cost after approval (current) - observed_cost_per_hour_before_bytes_usd REAL, -- Measured ingestion cost before approval - observed_cost_per_hour_before_usd REAL, -- Measured total cost before approval - observed_cost_per_hour_before_volume_usd REAL, -- Measured indexing cost before approval - observed_volume_per_hour_after REAL, -- Measured events/hour after policy approval (current) - observed_volume_per_hour_before REAL, -- Measured events/hour before first policy approval - pending_policy_count INTEGER, -- Policies awaiting user action - policy_count INTEGER, -- Total non-dismissed policies - policy_pending_critical_count INTEGER, -- Pending policies with critical compliance severity - policy_pending_high_count INTEGER, -- Pending policies with high compliance severity - policy_pending_low_count INTEGER, -- Pending policies with low compliance severity - policy_pending_medium_count INTEGER, -- Pending policies with medium compliance severity - service_id TEXT, -- Service ID (denormalized from log_event) - volume_per_hour REAL -- Current throughput in events/hour (rolling 7-day) + account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, + has_been_analyzed INTEGER, + has_effective_policy INTEGER, + has_preview_policy INTEGER, + has_volumes INTEGER, + log_event_id TEXT, + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + service_id TEXT ); -- Distinct log message pattern discovered within a service. Defines how to parse and match logs using codecs and matchers. @@ -272,21 +428,6 @@ CREATE TABLE log_events ( baseline_volume_per_hour REAL, -- Current trailing 7-day average events/hour. Refreshed on volume ingestion. created_at TEXT, -- When the log event was created description TEXT, -- What the event is and what data instances carry. Helps engineers decide whether to look here. - event_nature TEXT, -- What this event records: system (internal mechanics), traffic (request flow), activity (actor+action+resource), control (access/permission decisions). - -- Sample log records captured during discovery, used for AI analysis and pattern validation - -- JSON array of objects. Each element: - -- $[0].timestamp - When the log event occurred (RFC3339) - -- $[0].body string - Log message content - -- $[0].severity_text string - Severity level as text (e.g., INFO, ERROR) - -- $[0].severity_number number - OTel severity level number (1-24) - -- $[0].trace_id string - Distributed trace ID (optional) - -- $[0].span_id string - Span ID within trace (optional) - -- $[0].attributes object - Log-level attributes (http.status, error.message, etc.) - -- $[0].resource_attributes object - Resource attributes (service.name, deployment.environment, etc.) - -- $[0].scope_attributes object - Instrumentation scope attributes (optional) - -- - -- Example: json_extract(examples, '$[0].field_name') - examples TEXT, -- JSON rules that match incoming logs to this event. Each matcher specifies a field path, operator, and value. -- JSON array of objects. Each element: -- $[0].field_path[] string[] - Path to field as array of segments @@ -299,8 +440,7 @@ CREATE TABLE log_events ( matchers TEXT, name TEXT, -- Snake_case identifier unique per service, e.g. nginx_access_log service_id TEXT, -- Service that produces this event - severity TEXT, -- Predominant log severity level, derived from example records. Nullable when examples have no severity info. Values: debug, info, warn, error, other. - signal_purpose TEXT -- What role this event serves: diagnostic (investigate incidents), operational (system behavior), lifecycle (state transitions), ephemeral (transient state). + severity TEXT -- Predominant log severity level, derived from example records. Nullable when examples have no severity info. Values: debug = The event usually appears at debug severity.; info = The event usually appears at info severity.; warn = The event usually appears at warn severity.; error = The event usually appears at error severity.; other = The event usually appears at a non-standard or other severity. ); -- Single message in a chat conversation. Append-only — never updated or deleted. @@ -310,72 +450,130 @@ CREATE TABLE messages ( -- Array of typed content blocks: text, thinking, tool_use, tool_result -- JSON array of objects. Each element: -- $[0].type string - Block type discriminator: text, thinking, tool_use, tool_result - -- $[0].text - Text content (when type=text) + -- $[0].text object - Text content (when type=text) -- $[0].text.content string - Text content - -- $[0].thinking - AI reasoning content (when type=thinking) + -- $[0].thinking object - AI reasoning content (when type=thinking) -- $[0].thinking.content string - AI reasoning content - -- $[0].tool_use - Tool call (when type=tool_use) + -- $[0].tool_use object - Tool call (when type=tool_use) -- $[0].tool_use.id string - Unique tool call identifier -- $[0].tool_use.name string - Tool name - -- $[0].tool_use.input[] number[] - Tool input parameters as JSON object - -- $[0].tool_result - Tool response (when type=tool_result) + -- $[0].tool_use.input object - Tool input parameters as JSON object + -- $[0].tool_result object - Tool response (when type=tool_result) -- $[0].tool_result.tool_use_id string - ID of the tool use this result corresponds to -- $[0].tool_result.is_error boolean - Whether the tool call failed -- $[0].tool_result.error string - Human-readable error message (when is_error=true) - -- $[0].tool_result.content[] number[] - Structured result data (when is_error=false) + -- $[0].tool_result.content object - Structured result data (when is_error=false) -- -- Example: json_extract(content, '$[0].field_name') content TEXT, conversation_id TEXT, -- Conversation this message belongs to created_at TEXT, -- When the message was created model TEXT, -- AI model that produced this message. Null for user messages. - role TEXT, -- Who sent this message. user: human-originated, assistant: AI-originated. - stop_reason TEXT -- Why the assistant stopped generating. end_turn: completed response, tool_use: paused to call a tool. Null for user messages. + role TEXT, -- Who sent this message. Values: user = Human-originated message content.; assistant = AI-originated message content. + stop_reason TEXT -- Why the assistant stopped generating. Null for user messages. Values: end_turn = The assistant completed its response.; tool_use = The assistant paused to call a tool. +); + +-- Extensible typed facts attached to a service. +CREATE TABLE service_facts ( + id TEXT, -- Unique identifier + account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from service.account_id. + created_at TEXT, -- When this fact was first recorded + fact_group TEXT, -- Owned fact group refreshed together, e.g. catalog_service_facts. + fact_type TEXT, -- Fact type within the namespace, e.g. service_profile or actionability_profile. + namespace TEXT, -- Fact namespace, e.g. semantic or operational. + service_id TEXT, -- The service this fact belongs to + -- Typed fact payload stored as JSONB. Shape is determined by namespace + fact_type. + -- JSON shape depends on namespace + fact_type. + -- + -- Variant: semantic + service_profile + -- Describes what the service appears to be, what it is primarily responsible for, and the major role it plays in the system. + -- Fields: + -- $.summary string - A concise grounded summary of what the service appears to do. + -- $.service_category string - The best-fitting broad service category. Values: customer_api=Customer-facing request/response API service.; internal_api=Internal API or control-plane service.; background_worker=Asynchronous worker or job processor.; integration_adapter=Connector, bridge, or external integration adapter.; data_pipeline=Pipeline, stream processor, or ETL-style service.; platform_component=Platform or infrastructure component.; message_broker=Messaging broker or queueing component.; database=Database or durable storage engine.; cache=Cache or in-memory data service.; observability_component=Collector, agent, or observability infrastructure.; unknown=Insufficient evidence for a stronger classification.. + -- $.primary_responsibilities[] string[] - Short grounded statements of the service's primary responsibilities. + -- $.system_roles[] string[] - Major system roles the service appears to play. Values: request_handling=Handles requests or API traffic.; background_processing=Processes asynchronous or scheduled work.; integration=Connects to or mediates external systems.; state_management=Stores or manages application state.; messaging=Publishes, consumes, or routes messages.; platform_operations=Performs platform or infrastructure operations.. + -- + -- Variant: semantic + telemetry_profile + -- Describes the broad shape of telemetry this service emits, including the main log patterns and notable telemetry characteristics. + -- Fields: + -- $.summary string - A concise grounded summary of what the service's logs mostly look like. + -- $.dominant_log_patterns[] string[] - The main classes of logs the service appears to emit. Values: request_logs=Request or access logs.; job_logs=Background job or worker logs.; integration_logs=External-system integration logs.; lifecycle_logs=Startup, shutdown, and lifecycle logs.; platform_housekeeping_logs=Platform or housekeeping logs.; audit_logs=Audit or compliance-oriented logs.. + -- $.dominant_operational_roles[] string[] - Plain-language descriptions of the main operational roles visible in the logs. + -- $.telemetry_characteristics[] string[] - Broad traits of the service's telemetry. Values: high_volume=High log volume relative to the visible behaviors.; error_heavy=Errors are a large visible part of the stream.; infra_heavy=Infrastructure or platform logs dominate the stream.; integration_heavy=Integration activity dominates the stream.; state_transition_heavy=State-change or workflow logs dominate the stream.; mixed_signal_quality=The stream mixes high-value and low-value signal quality.. + -- + -- Variant: operational + actionability_profile + -- Describes how much direct control the team likely has over this service and which change levers are most realistic when a finding affects it. + -- Fields: + -- $.summary string - A concise grounded summary of how actionable findings against this service are likely to be. + -- $.service_origin string - Where this service likely comes from. Values: first_party=Application or service written and owned by the team.; third_party=Third-party product or vendor-managed service.; open_source_component=Open-source component the team operates.; managed_platform=Managed platform or infrastructure service.; unknown=Insufficient evidence for a stronger classification.. + -- $.ownership_level string - How much direct control the team likely has over behavior changes. Values: owned_code=The team can directly change the service code.; configured_only=The team mostly influences behavior through configuration.; operated_only=The team mainly operates the service but does not own its code.; unknown=Insufficient evidence for a stronger classification.. + -- $.change_levers[] string[] - The most realistic levers available for changing this service or its telemetry. Values: application_code=Application code changes.; service_config=Service-level configuration changes.; deployment_config=Deployment or infrastructure-as-code changes.; collector_config=Collector or telemetry-pipeline configuration changes.; platform_tuning=Platform tuning or operational settings.. + -- + -- Variant: operational + shared_payload_hygiene_profile + -- Describes the overall weight and non-essential noise of shared service-level telemetry metadata, along with the exact expensive shared paths. + -- Fields: + -- $.shared_payload_weight string - The overall payload weight of the shared service-level metadata. Values: lean=Shared metadata is compact and inexpensive.; moderate=Shared metadata is noticeable but not dominating.; heavy=Shared metadata is heavy and materially increases payload cost.. + -- $.shared_enrichment_noise string - How much non-essential shared enrichment surrounds the service's logs. Values: none=There is no meaningful shared enrichment noise.; low=There is limited shared enrichment noise.; medium=There is noticeable shared enrichment noise.; high=There is heavy shared enrichment noise.. + -- $.expensive_shared_paths[] string[][] - Concrete shared paths that materially increase cost or clutter across this service's logs. + -- + -- Example: json_extract(value, '$.field_name') with the matching discriminator columns. + value TEXT, + version INTEGER -- Current code version applied for this fact group. ); --- Cache table for service_statuses view. Refreshed by cron service. +-- Cache table for canonical service_statuses view. Refreshed by worker-owned status loops. CREATE TABLE service_statuses_cache ( id TEXT, - account_id TEXT, -- Account ID (denormalized from service) - datadog_account_id TEXT, -- The Datadog account performing discovery - estimated_bytes_reduction_per_hour REAL, -- Estimated bytes reduction from active policies - estimated_cost_reduction_per_hour_bytes_usd REAL, -- Estimated bytes-based USD/hour savings from active policies - estimated_cost_reduction_per_hour_usd REAL, -- Estimated total USD/hour savings from active policies - estimated_cost_reduction_per_hour_volume_usd REAL, -- Estimated volume-based USD/hour savings from active policies - estimated_volume_reduction_per_hour REAL, -- Estimated volume reduction from active policies - health TEXT, -- Overall health of the service. DISABLED (integration turned off), INACTIVE (no data received), OK (healthy). - log_event_analyzed_count INTEGER, -- Number of log events that have been analyzed - log_event_bytes_per_hour REAL, -- Discovered log event throughput in bytes/hour from rolling 7-day window - log_event_cost_per_hour_bytes_usd REAL, -- Discovered log event ingestion cost in USD/hour - log_event_cost_per_hour_usd REAL, -- Discovered log event total cost in USD/hour (bytes + volume) - log_event_cost_per_hour_volume_usd REAL, -- Discovered log event indexing cost in USD/hour - log_event_count INTEGER, -- Total number of log events discovered for this service - log_event_volume_per_hour REAL, -- Discovered log event throughput in events/hour from rolling 7-day window - observed_bytes_per_hour_after REAL, -- Measured bytes/hour after policy approval - observed_bytes_per_hour_before REAL, -- Measured bytes/hour before first policy approval - observed_cost_per_hour_after_bytes_usd REAL, -- Measured bytes-based USD/hour cost after approval - observed_cost_per_hour_after_usd REAL, -- Measured total USD/hour cost after approval - observed_cost_per_hour_after_volume_usd REAL, -- Measured volume-based USD/hour cost after approval - observed_cost_per_hour_before_bytes_usd REAL, -- Measured bytes-based USD/hour cost before approval - observed_cost_per_hour_before_usd REAL, -- Measured total USD/hour cost before approval - observed_cost_per_hour_before_volume_usd REAL, -- Measured volume-based USD/hour cost before approval - observed_volume_per_hour_after REAL, -- Measured events/hour after policy approval - observed_volume_per_hour_before REAL, -- Measured events/hour before first policy approval - policy_approved_count INTEGER, -- Policies approved by user - policy_dismissed_count INTEGER, -- Policies dismissed by user - policy_pending_count INTEGER, -- Policies awaiting user action - policy_pending_critical_count INTEGER, -- Pending policies with critical compliance severity - policy_pending_high_count INTEGER, -- Pending policies with high compliance severity - policy_pending_low_count INTEGER, -- Pending policies with low compliance severity - policy_pending_medium_count INTEGER, -- Pending policies with medium compliance severity - service_cost_per_hour_volume_usd REAL, -- Service-level indexing cost in USD/hour based on total service volume - service_debug_volume_per_hour REAL, -- Debug-level events/hour from rolling 7-day window - service_error_volume_per_hour REAL, -- Error-level events/hour from rolling 7-day window - service_id TEXT, -- The service this status belongs to - service_info_volume_per_hour REAL, -- Info-level events/hour from rolling 7-day window - service_other_volume_per_hour REAL, -- Other-level events/hour (trace, fatal, critical, unknown) from rolling 7-day window - service_volume_per_hour REAL, -- Ground-truth service throughput in events/hour from service_log_volumes rolling 7-day window - service_warn_volume_per_hour REAL -- Warn-level events/hour from rolling 7-day window + account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_service_debug_events_per_hour REAL, + current_service_error_events_per_hour REAL, + current_service_events_per_hour REAL, + current_service_info_events_per_hour REAL, + current_service_other_events_per_hour REAL, + current_service_volume_usd_per_hour REAL, + current_service_warn_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + datadog_account_id TEXT, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_log_event_count INTEGER, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, + health TEXT, -- Health state for this status row. Values: DISABLED = The user turned the log pipeline off.; INACTIVE = The service currently has no log volume.; ERROR = The log pipeline is unhealthy or failing.; OK = The log pipeline is healthy. + log_event_analyzed_count INTEGER, + log_event_count INTEGER, + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_log_event_count INTEGER, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + service_id TEXT +); + +-- Maps a service to its owning team. +CREATE TABLE service_team_mappings ( + id TEXT, -- Unique identifier of the service-team mapping + account_id TEXT, -- Denormalized account for RLS. Auto-set via trigger from service.account_id. + created_at TEXT, -- When the mapping was created + service_id TEXT, -- Service assigned to a team + team_id TEXT, -- Owning team for the service + updated_at TEXT -- When the mapping was last updated ); -- Application or microservice that produces logs. Central entity in the data catalog. @@ -383,49 +581,38 @@ CREATE TABLE services ( id TEXT, -- Unique identifier of the service account_id TEXT, -- Parent account this service belongs to created_at TEXT, -- When the service was created - description TEXT, -- AI-generated description of what this service does and its telemetry characteristics enabled INTEGER, -- Whether log analysis and policy generation is active for this service - initial_weekly_log_count INTEGER, -- Approximate weekly log count from initial discovery (7-day period from Datadog) + initial_weekly_log_count INTEGER, -- Approximate weekly log count from initial catalog loop pass (7-day period from Datadog) name TEXT -- Service identifier in telemetry (e.g., 'checkout-service') ); --- Group of users within a workspace that reviews policies and manages services +-- Membership of a WorkOS user in an organization-scoped team. +CREATE TABLE team_memberships ( + id TEXT, -- Unique identifier of the team membership + created_at TEXT, -- When the membership was created + organization_id TEXT, -- Denormalized organization for RLS. Auto-set via trigger from team.organization_id. + team_id TEXT, -- Team this membership belongs to + updated_at TEXT, -- When the membership was last updated + user_id TEXT -- WorkOS user ID assigned to the team +); + +-- Organization-scoped group of users used for service ownership and filtering. CREATE TABLE teams ( id TEXT, -- Unique identifier of the team - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. created_at TEXT, -- When the team was created - name TEXT, -- Human-readable name within the workspace - workspace_id TEXT -- Parent workspace this team belongs to -); - --- User's saved reference to a view, elevating it from conversation history to their personal collection -CREATE TABLE view_favorites ( - id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from view.account_id. - created_at TEXT, -- When the view was favorited - user_id TEXT, -- WorkOS user ID who favorited this view - view_id TEXT -- The view being favorited -); - --- Saved SQL query against the local catalog, created by the AI assistant. Immutable — editing creates a fork. -CREATE TABLE views ( - id TEXT, -- Unique identifier - account_id TEXT, -- Denormalized for tenant isolation. Auto-set via trigger from message.account_id. - conversation_id TEXT, -- Denormalized from message for easier queries - created_at TEXT, -- When the view was created - created_by TEXT, -- WorkOS user ID who triggered this view creation - entity_type TEXT, -- Which catalog entity this view queries. service: applications, log_event: event patterns, policy: quality recommendations. - forked_from_id TEXT, -- Parent view if this is a refinement/iteration - message_id TEXT, -- Assistant message that created this view via show_view tool call - query TEXT -- Raw SQL query executed against the client's local SQLite database + description TEXT, -- Optional description of the team's scope and responsibilities + external_id TEXT, -- Optional external group identifier reserved for future SCIM mapping + name TEXT, -- Human-readable team name within the organization + organization_id TEXT, -- Organization this team belongs to + updated_at TEXT -- When the team was last updated ); --- Purpose-aligned environment for reviewing and classifying telemetry. Each workspace has its own policies and teams. +-- Purpose-aligned environment for reviewing and classifying telemetry. CREATE TABLE workspaces ( id TEXT, -- Unique identifier of the workspace account_id TEXT, -- Parent account this workspace belongs to created_at TEXT, -- When the workspace was created name TEXT, -- Human-readable name within the account - purpose TEXT -- Primary purpose determining evaluation strategy. observability: performance and reliability, security: threat detection, compliance: regulatory requirements. + purpose TEXT -- Primary purpose determining evaluation strategy. Values: observability = Performance, reliability, and operational visibility.; security = Threat detection, investigation, and security posture.; compliance = Regulatory, privacy, and policy compliance review. ); diff --git a/internal/app/onboarding/gate_requirements_test.go b/internal/app/onboarding/gate_requirements_test.go index 18a1e80..d42b078 100644 --- a/internal/app/onboarding/gate_requirements_test.go +++ b/internal/app/onboarding/gate_requirements_test.go @@ -21,8 +21,7 @@ func TestRewindGateFor(t *testing.T) { {name: "datadog api rewinds to region when site missing", target: bootstrap.GateDatadogAPIKey, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1")}, want: bootstrap.GateDatadogRegion}, {name: "datadog app rewinds to api key when api key missing", target: bootstrap.GateDatadogAppKey, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1"), DDSite: "US1"}, want: bootstrap.GateDatadogAPIKey}, {name: "discovery rewinds to datadog check without dd account", target: bootstrap.GateDatadogDiscovery, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1")}, want: bootstrap.GateDatadogCheck}, - {name: "sync rewinds to workspace", target: bootstrap.GateSync, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1")}, want: bootstrap.GateWorkspaceSelect}, - {name: "sync stays when requirements met", target: bootstrap.GateSync, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1"), Workspace: ptrWorkspace("ws-1")}, want: bootstrap.GateSync}, + {name: "sync stays when account is selected", target: bootstrap.GateSync, state: bootstrap.State{Org: ptrOrg("org-1"), Account: ptrAccount("acc-1")}, want: bootstrap.GateSync}, } for _, tc := range tests { diff --git a/internal/app/onboarding/preflight/preflight_effects.go b/internal/app/onboarding/preflight/preflight_effects.go index 56a7810..2d1eccd 100644 --- a/internal/app/onboarding/preflight/preflight_effects.go +++ b/internal/app/onboarding/preflight/preflight_effects.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" + iauth "github.com/usetero/cli/internal/auth" "github.com/usetero/cli/internal/core/bootstrap" "github.com/usetero/cli/internal/domain" ) @@ -13,14 +14,18 @@ import ( func (m *Model) checkAuth() tea.Cmd { return func() tea.Msg { hasValidAuth := false + var user *iauth.User if m.auth.IsAuthenticated() { if _, err := m.auth.GetAccessToken(m.ctx); err == nil { hasValidAuth = true + if userID, err := m.auth.GetUserID(m.ctx); err == nil && userID != "" { + user = &iauth.User{ID: userID} + } } else { _ = m.auth.ClearTokens() } } - return preflightAuthCheckCompletedMsg{hasValidAuth: hasValidAuth} + return preflightAuthCheckCompletedMsg{hasValidAuth: hasValidAuth, user: user} } } diff --git a/internal/app/onboarding/preflight/preflight_types.go b/internal/app/onboarding/preflight/preflight_types.go index eef1e87..e0c57e3 100644 --- a/internal/app/onboarding/preflight/preflight_types.go +++ b/internal/app/onboarding/preflight/preflight_types.go @@ -1,6 +1,7 @@ package preflight import ( + "github.com/usetero/cli/internal/auth" "github.com/usetero/cli/internal/core/bootstrap" "github.com/usetero/cli/internal/domain" ) @@ -11,6 +12,7 @@ type preflightResolutionCompletedMsg struct { type preflightAuthCheckCompletedMsg struct { hasValidAuth bool + user *auth.User } type preflightOrganizationsLoadedMsg struct { diff --git a/internal/app/onboarding/preflight/preflight_update.go b/internal/app/onboarding/preflight/preflight_update.go index 9ad688b..b1ac51a 100644 --- a/internal/app/onboarding/preflight/preflight_update.go +++ b/internal/app/onboarding/preflight/preflight_update.go @@ -29,6 +29,7 @@ func (m *Model) Update(msg tea.Msg) tea.Cmd { func (m *Model) handleAuthChecked(msg preflightAuthCheckCompletedMsg) tea.Cmd { m.state.HasValidAuth = msg.hasValidAuth + m.state.User = msg.user if !m.state.HasValidAuth { return m.emitResult() } diff --git a/internal/app/onboarding/transition_cmds.go b/internal/app/onboarding/transition_cmds.go index 4ee3d58..c28061c 100644 --- a/internal/app/onboarding/transition_cmds.go +++ b/internal/app/onboarding/transition_cmds.go @@ -23,14 +23,12 @@ func (m *Model) commandForTransition(event bootstrap.Event, transition bootstrap m.scope.Info("onboarding complete", slog.String("org_id", transition.Completion.Org.ID.String()), slog.String("account_id", transition.Completion.Account.ID.String()), - slog.String("workspace_id", string(transition.Completion.Workspace.ID)), ) return func() tea.Msg { return bootstrap.OnboardingComplete{ - User: transition.Completion.User, - Org: transition.Completion.Org, - Account: transition.Completion.Account, - Workspace: transition.Completion.Workspace, + User: transition.Completion.User, + Org: transition.Completion.Org, + Account: transition.Completion.Account, } } case bootstrap.TransitionNoop: diff --git a/internal/app/onboarding/transitions_test.go b/internal/app/onboarding/transitions_test.go index 08709ad..45653b3 100644 --- a/internal/app/onboarding/transitions_test.go +++ b/internal/app/onboarding/transitions_test.go @@ -99,6 +99,27 @@ func TestHandleTransitionPreflightRouting(t *testing.T) { } } +func TestHandleTransitionPreflightResolvedCarriesUserState(t *testing.T) { + t.Parallel() + + m := newTestModel(t) + user := ptrUser("user-1") + + cmd := m.handleTransition(bootstrap.PreflightResolved{State: bootstrap.PreflightState{ + HasValidAuth: true, + User: user, + Role: bootstrap.RolePlatform, + Org: ptrOrg("org-1"), + Account: ptrAccount("acc-1"), + }}) + if cmd == nil { + t.Fatal("expected command") + } + if m.state.User == nil || m.state.User.ID != user.ID { + t.Fatalf("user state = %+v, want %s", m.state.User, user.ID) + } +} + func TestHandleTransitionDatadogBranchRouting(t *testing.T) { t.Parallel() @@ -107,9 +128,9 @@ func TestHandleTransitionDatadogBranchRouting(t *testing.T) { msg any wantGate Gate }{ - {name: "datadog ready goes to workspace select", msg: bootstrap.DatadogReady{}, wantGate: bootstrap.GateWorkspaceSelect}, + {name: "datadog ready goes to sync", msg: bootstrap.DatadogReady{}, wantGate: bootstrap.GateSync}, {name: "datadog needed goes to region", msg: bootstrap.DatadogNeeded{}, wantGate: bootstrap.GateDatadogRegion}, - {name: "discovery complete goes to workspace select", msg: bootstrap.DatadogDiscoveryComplete{}, wantGate: bootstrap.GateWorkspaceSelect}, + {name: "discovery complete goes to sync", msg: bootstrap.DatadogDiscoveryComplete{}, wantGate: bootstrap.GateSync}, } for _, tc := range tests { @@ -233,7 +254,6 @@ func TestHandleTransitionSyncComplete(t *testing.T) { m.state.User = ptrUser("user-1") m.state.Org = ptrOrg("org-1") m.state.Account = ptrAccount("acc-1") - m.state.Workspace = ptrWorkspace("ws-1") cmd := m.handleTransition(bootstrap.SyncComplete{}) if cmd == nil { @@ -244,7 +264,7 @@ func TestHandleTransitionSyncComplete(t *testing.T) { if !ok { t.Fatalf("message type = %T, want bootstrap.OnboardingComplete", msg) } - if complete.Org.ID != "org-1" || complete.Account.ID != "acc-1" || complete.Workspace.ID != "ws-1" || complete.User.ID != "user-1" { + if complete.Org.ID != "org-1" || complete.Account.ID != "acc-1" || complete.User.ID != "user-1" { t.Fatalf("unexpected completion payload: %+v", complete) } } @@ -255,7 +275,7 @@ func TestHandleTransitionSyncCompleteMissingStateNoops(t *testing.T) { m := newTestModel(t) m.state.User = ptrUser("user-1") m.state.Org = ptrOrg("org-1") - // Missing account/workspace should not panic or emit completion payload. + // Missing account should not panic or emit completion payload. cmd := m.handleTransition(bootstrap.SyncComplete{}) if cmd != nil { diff --git a/internal/app/onboarding_orchestration.go b/internal/app/onboarding_orchestration.go index 62e6dee..8a8c9e2 100644 --- a/internal/app/onboarding_orchestration.go +++ b/internal/app/onboarding_orchestration.go @@ -47,11 +47,9 @@ func (m *Model) handleOnboardingMessage(msg tea.Msg) (tea.Cmd, bool) { m.state = stateChat m.user = msg.User m.account = msg.Account - m.workspace = msg.Workspace m.scope.Info("onboarding complete", "org", msg.Org.Name, "account", msg.Account.Name, - "workspace", msg.Workspace.Name, ) // Create chat model (sizing happens via updateLayout) diff --git a/internal/boundary/graphql/conversation_service.go b/internal/boundary/graphql/conversation_service.go index 678ded9..abf553e 100644 --- a/internal/boundary/graphql/conversation_service.go +++ b/internal/boundary/graphql/conversation_service.go @@ -13,7 +13,9 @@ import ( // CreateConversationInput contains the fields for creating a conversation. type CreateConversationInput struct { - ID uuid.UUID + ID uuid.UUID + AccountID domain.AccountID + // WorkspaceID is kept only for legacy server compatibility. WorkspaceID domain.WorkspaceID Title string } @@ -50,12 +52,16 @@ func NewConversationService(client Client, scope log.Scope) *ConversationService // Create creates a new conversation with the given client-provided ID. func (s *ConversationService) Create(ctx context.Context, input CreateConversationInput) (*domain.Conversation, error) { - s.scope.Debug("creating conversation via API", "id", input.ID.String(), "workspaceID", input.WorkspaceID.String(), "title", input.Title) + s.scope.Debug("creating conversation via API", "id", input.ID.String(), "accountID", input.AccountID.String(), "workspaceID", input.WorkspaceID.String(), "title", input.Title) genInput := gen.CreateConversationInput{ - Id: ptr(input.ID.String()), - WorkspaceID: input.WorkspaceID.String(), - Title: ptr(input.Title), + Id: ptr(input.ID.String()), + Title: ptr(input.Title), + } + if input.AccountID != "" { + genInput.AccountID = ptr(input.AccountID.String()) + } else if input.WorkspaceID != "" { + genInput.WorkspaceID = ptr(input.WorkspaceID.String()) } resp, err := s.client.CreateConversation(ctx, genInput) @@ -68,9 +74,8 @@ func (s *ConversationService) Create(ctx context.Context, input CreateConversati } conversation := &domain.Conversation{ - ID: domain.ConversationID(resp.CreateConversation.Id), - WorkspaceID: input.WorkspaceID, - Title: input.Title, + ID: domain.ConversationID(resp.CreateConversation.Id), + Title: input.Title, } s.scope.Debug("created conversation via API", "id", conversation.ID) diff --git a/internal/boundary/graphql/conversation_service_test.go b/internal/boundary/graphql/conversation_service_test.go new file mode 100644 index 0000000..f231864 --- /dev/null +++ b/internal/boundary/graphql/conversation_service_test.go @@ -0,0 +1,106 @@ +package graphql_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + graphql "github.com/usetero/cli/internal/boundary/graphql" + "github.com/usetero/cli/internal/boundary/graphql/apitest" + "github.com/usetero/cli/internal/boundary/graphql/gen" + "github.com/usetero/cli/internal/log/logtest" +) + +func TestConversationService_Create_UsesCurrentContract(t *testing.T) { + t.Parallel() + + var capturedInput gen.CreateConversationInput + mockClient := &apitest.MockClient{ + CreateConversationFunc: func(ctx context.Context, input gen.CreateConversationInput) (*gen.CreateConversationResponse, error) { + capturedInput = input + return &gen.CreateConversationResponse{ + CreateConversation: gen.CreateConversationCreateConversation{ + Id: "conv-1", + }, + }, nil + }, + } + + svc := graphql.NewConversationService(mockClient, logtest.NewScope(t)) + _, err := svc.Create(context.Background(), graphql.CreateConversationInput{ + ID: uuid.New(), + AccountID: "acc-123", + Title: "Test conversation", + }) + if err != nil { + t.Fatalf("Create() error = %v", err) + } + + if capturedInput.AccountID == nil || *capturedInput.AccountID != "acc-123" { + t.Fatalf("AccountID = %v, want acc-123", capturedInput.AccountID) + } + if capturedInput.WorkspaceID != nil { + t.Fatalf("WorkspaceID = %v, want nil", *capturedInput.WorkspaceID) + } +} + +func TestConversationService_Update_UsesCurrentContract(t *testing.T) { + t.Parallel() + + var capturedInput gen.UpdateConversationInput + mockClient := &apitest.MockClient{ + UpdateConversationFunc: func(ctx context.Context, id string, input gen.UpdateConversationInput) (*gen.UpdateConversationResponse, error) { + capturedInput = input + return &gen.UpdateConversationResponse{ + UpdateConversation: gen.UpdateConversationUpdateConversation{ + Id: id, + Title: input.Title, + }, + }, nil + }, + } + + svc := graphql.NewConversationService(mockClient, logtest.NewScope(t)) + title := "Renamed" + _, err := svc.Update(context.Background(), "conv-1", graphql.UpdateConversationInput{Title: &title}) + if err != nil { + t.Fatalf("Update() error = %v", err) + } + + if capturedInput.Title == nil || *capturedInput.Title != title { + t.Fatalf("Title = %v, want %q", capturedInput.Title, title) + } + if capturedInput.ClearTitle != nil { + t.Fatalf("ClearTitle = %v, want nil", *capturedInput.ClearTitle) + } +} + +func TestConversationService_Update_ClearsTitleWithCurrentContract(t *testing.T) { + t.Parallel() + + var capturedInput gen.UpdateConversationInput + mockClient := &apitest.MockClient{ + UpdateConversationFunc: func(ctx context.Context, id string, input gen.UpdateConversationInput) (*gen.UpdateConversationResponse, error) { + capturedInput = input + return &gen.UpdateConversationResponse{ + UpdateConversation: gen.UpdateConversationUpdateConversation{ + Id: id, + }, + }, nil + }, + } + + svc := graphql.NewConversationService(mockClient, logtest.NewScope(t)) + empty := "" + _, err := svc.Update(context.Background(), "conv-1", graphql.UpdateConversationInput{Title: &empty}) + if err != nil { + t.Fatalf("Update() error = %v", err) + } + + if capturedInput.Title != nil { + t.Fatalf("Title = %v, want nil", *capturedInput.Title) + } + if capturedInput.ClearTitle == nil || !*capturedInput.ClearTitle { + t.Fatalf("ClearTitle = %v, want true", capturedInput.ClearTitle) + } +} diff --git a/internal/boundary/graphql/gen/generated.go b/internal/boundary/graphql/gen/generated.go index 6ea4871..6ad962c 100644 --- a/internal/boundary/graphql/gen/generated.go +++ b/internal/boundary/graphql/gen/generated.go @@ -174,9 +174,11 @@ func (v *CreateConversationCreateConversation) GetUpdatedAt() time.Time { return // Input was generated by ent. type CreateConversationInput struct { // AI-generated title, set after first exchange - Title *string `json:"title"` - WorkspaceID string `json:"workspaceID"` - ViewID *string `json:"viewID"` + Title *string `json:"title"` + // Account-scoped create path used by current clients. + AccountID *string `json:"accountID"` + // Legacy workspace-scoped create path used by older clients. + WorkspaceID *string `json:"workspaceID"` // Optional client-provided UUID for offline-first sync. // If provided and a conversation with this ID exists, returns the existing record. Id *string `json:"id"` @@ -185,18 +187,18 @@ type CreateConversationInput struct { // GetTitle returns CreateConversationInput.Title, and is useful for accessing the field via an interface. func (v *CreateConversationInput) GetTitle() *string { return v.Title } -// GetWorkspaceID returns CreateConversationInput.WorkspaceID, and is useful for accessing the field via an interface. -func (v *CreateConversationInput) GetWorkspaceID() string { return v.WorkspaceID } +// GetAccountID returns CreateConversationInput.AccountID, and is useful for accessing the field via an interface. +func (v *CreateConversationInput) GetAccountID() *string { return v.AccountID } -// GetViewID returns CreateConversationInput.ViewID, and is useful for accessing the field via an interface. -func (v *CreateConversationInput) GetViewID() *string { return v.ViewID } +// GetWorkspaceID returns CreateConversationInput.WorkspaceID, and is useful for accessing the field via an interface. +func (v *CreateConversationInput) GetWorkspaceID() *string { return v.WorkspaceID } // GetId returns CreateConversationInput.Id, and is useful for accessing the field via an interface. func (v *CreateConversationInput) GetId() *string { return v.Id } // CreateConversationResponse is returned by CreateConversation on success. type CreateConversationResponse struct { - // Create a new conversation in a workspace. + // Create a new conversation in the current account. // The conversation is owned by the authenticated user. CreateConversation CreateConversationCreateConversation `json:"createConversation"` } @@ -2179,8 +2181,6 @@ type UpdateConversationInput struct { // AI-generated title, set after first exchange Title *string `json:"title"` ClearTitle *bool `json:"clearTitle"` - ViewID *string `json:"viewID"` - ClearView *bool `json:"clearView"` } // GetTitle returns UpdateConversationInput.Title, and is useful for accessing the field via an interface. @@ -2189,12 +2189,6 @@ func (v *UpdateConversationInput) GetTitle() *string { return v.Title } // GetClearTitle returns UpdateConversationInput.ClearTitle, and is useful for accessing the field via an interface. func (v *UpdateConversationInput) GetClearTitle() *bool { return v.ClearTitle } -// GetViewID returns UpdateConversationInput.ViewID, and is useful for accessing the field via an interface. -func (v *UpdateConversationInput) GetViewID() *string { return v.ViewID } - -// GetClearView returns UpdateConversationInput.ClearView, and is useful for accessing the field via an interface. -func (v *UpdateConversationInput) GetClearView() *bool { return v.ClearView } - // UpdateConversationResponse is returned by UpdateConversation on success. type UpdateConversationResponse struct { // Update a conversation (e.g., set title). diff --git a/internal/boundary/graphql/gen/schema.graphql b/internal/boundary/graphql/gen/schema.graphql index 809b185..9290623 100644 --- a/internal/boundary/graphql/gen/schema.graphql +++ b/internal/boundary/graphql/gen/schema.graphql @@ -1,111 +1,116 @@ -""" -Directs the executor to defer this fragment when the `if` argument is true or undefined. -""" -directive @defer( - """Deferred when true or undefined.""" - if: Boolean = true - - """Unique name""" - label: String -) on FRAGMENT_SPREAD | INLINE_FRAGMENT +scalar UUID directive @goField(forceResolver: Boolean, name: String, omittable: Boolean) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - directive @goModel(model: String, models: [String!], forceGenerate: Boolean) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION - -""" -Indicates exactly one field must be supplied and this field must not be `null`. -""" -directive @oneOf on INPUT_OBJECT - -"""Exposes a URL that specifies the behavior of this scalar.""" -directive @specifiedBy( - """The URL that specifies the behavior of this scalar.""" - url: String! -) on SCALAR - type Account implements Node { - """Unique identifier of the account""" + """ + Unique identifier of the account + """ id: ID! - - """Parent organization this account belongs to""" + """ + Parent organization this account belongs to + """ organizationID: ID! - """ Denormalized for PowerSync. Auto-set via trigger from organization.workos_organization_id. """ workosOrganizationID: String! - - """Human-readable name within the organization""" + """ + Human-readable name within the organization + """ name: String! - """ Multiplier applied to volume data via trigger. 1 = real data, >1 = scaled for demos. """ demoScaleFactor: Int! - - """When the account was created""" + """ + When the account was created + """ createdAt: Time! - - """When the account was last updated""" + """ + When the account was last updated + """ updatedAt: Time! - - """Organization this account belongs to""" + """ + Organization this account belongs to + """ organization: Organization! - - """Services that produce telemetry""" + """ + Services that produce telemetry + """ services: [Service!] - - """Purpose-aligned workspaces for telemetry evaluation""" + """ + Chat conversations in this account + """ + conversations: [Conversation!] + """ + Purpose-aligned workspaces for telemetry evaluation + """ workspaces: [Workspace!] - - """Datadog integration configuration""" + """ + Datadog integration configuration + """ datadogAccount: DatadogAccount - - """Edge instances that sync policies from this account""" + """ + Edge instances that sync policies from this account + """ edgeInstances: [EdgeInstance!] - - """API keys for edge instance authentication""" + """ + API keys for edge instance authentication + """ edgeAPIKeys: [EdgeApiKey!] } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type AccountConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [AccountEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type AccountEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: Account - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for Account connections""" +""" +Ordering options for Account connections +""" input AccountOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order Accounts.""" + """ + The field by which to order Accounts. + """ field: AccountOrderField! } - -"""Properties by which Account connections can be ordered.""" +""" +Properties by which Account connections can be ordered. +""" enum AccountOrderField { NAME CREATED_AT UPDATED_AT } - """ AccountWhereInput is used for filtering Account objects. Input was generated by ent. @@ -114,8 +119,9 @@ input AccountWhereInput { not: AccountWhereInput and: [AccountWhereInput!] or: [AccountWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -124,14 +130,16 @@ input AccountWhereInput { idGTE: ID idLT: ID idLTE: ID - - """organization_id field predicates""" + """ + organization_id field predicates + """ organizationID: ID organizationIDNEQ: ID organizationIDIn: [ID!] organizationIDNotIn: [ID!] - - """workos_organization_id field predicates""" + """ + workos_organization_id field predicates + """ workosOrganizationID: String workosOrganizationIDNEQ: String workosOrganizationIDIn: [String!] @@ -145,8 +153,9 @@ input AccountWhereInput { workosOrganizationIDHasSuffix: String workosOrganizationIDEqualFold: String workosOrganizationIDContainsFold: String - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -160,8 +169,9 @@ input AccountWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """demo_scale_factor field predicates""" + """ + demo_scale_factor field predicates + """ demoScaleFactor: Int demoScaleFactorNEQ: Int demoScaleFactorIn: [Int!] @@ -170,8 +180,9 @@ input AccountWhereInput { demoScaleFactorGTE: Int demoScaleFactorLT: Int demoScaleFactorLTE: Int - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -180,8 +191,9 @@ input AccountWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -190,206 +202,137 @@ input AccountWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """organization edge predicates""" + """ + organization edge predicates + """ hasOrganization: Boolean hasOrganizationWith: [OrganizationWhereInput!] - - """services edge predicates""" + """ + services edge predicates + """ hasServices: Boolean hasServicesWith: [ServiceWhereInput!] - - """workspaces edge predicates""" + """ + conversations edge predicates + """ + hasConversations: Boolean + hasConversationsWith: [ConversationWhereInput!] + """ + workspaces edge predicates + """ hasWorkspaces: Boolean hasWorkspacesWith: [WorkspaceWhereInput!] - - """datadog_account edge predicates""" + """ + datadog_account edge predicates + """ hasDatadogAccount: Boolean hasDatadogAccountWith: [DatadogAccountWhereInput!] - - """edge_instances edge predicates""" + """ + edge_instances edge predicates + """ hasEdgeInstances: Boolean hasEdgeInstancesWith: [EdgeInstanceWhereInput!] - - """edge_api_keys edge predicates""" + """ + edge_api_keys edge predicates + """ hasEdgeAPIKeys: Boolean hasEdgeAPIKeysWith: [EdgeApiKeyWhereInput!] } - -""" -A content block in a message. Exactly one of the typed fields is set based on type. -""" -type ContentBlock { - type: ContentBlockType! - text: TextBlock - thinking: ThinkingBlock - toolUse: ToolUse - toolResult: ToolResult -} - -""" -A content block in a message. Exactly one of the typed fields should be set. -""" -input ContentBlockInput { - type: ContentBlockType! - text: TextBlockInput - thinking: ThinkingBlockInput - toolUse: ToolUseInput - toolResult: ToolResultInput -} - -"""The type of content block.""" -enum ContentBlockType { - text - thinking - tool_use - tool_result -} - type Conversation implements Node { - """Unique identifier""" + """ + Unique identifier + """ id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. + Account this conversation belongs to + """ + accountID: ID! + """ + WorkOS user ID who owns this conversation """ - accountID: UUID! - - """Workspace this conversation belongs to""" - workspaceID: ID! - - """If set, this conversation is for iterating on a specific view""" - viewID: ID - - """WorkOS user ID who owns this conversation""" userID: String! - - """AI-generated title, set after first exchange""" + """ + AI-generated title, set after first exchange + """ title: String - - """When the conversation was created""" + """ + When the conversation was created + """ createdAt: Time! - - """When the conversation was last updated""" + """ + When the conversation was last updated + """ updatedAt: Time! - - """Workspace this conversation belongs to""" - workspace: Workspace! - - """Messages in this conversation""" + """ + Account this conversation belongs to + """ + account: Account! + """ + Messages in this conversation + """ messages: [Message!] - - """Active entity context set""" - conversationContexts: [ConversationContext!] - - """Views created in this conversation""" - views: [View!] - - """View being iterated on, if this is a view iteration conversation""" - view: View } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type ConversationConnection { - """A list of edges.""" - edges: [ConversationEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" - totalCount: Int! -} - -type ConversationContext implements Node { - """Unique identifier""" - id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from conversation.account_id. + A list of edges. """ - accountID: UUID! - - """Conversation this context belongs to""" - conversationID: ID! - + edges: [ConversationEdge] """ - Type of the context entity. service: an application producing logs, log_event: a specific event pattern. + Information to aid in pagination. """ - entityType: ConversationContextEntityType! - - """ID of the context entity""" - entityID: UUID! - + pageInfo: PageInfo! """ - Who added this entity to context. user: added via @-reference, assistant: added by AI during chat. + Identifies the total count of items in the connection. """ - addedBy: ConversationContextAddedBy! - - """When the entity was added to context""" - createdAt: Time! - - """Conversation this context belongs to""" - conversation: Conversation! -} - -"""ConversationContextAddedBy is enum for the field added_by""" -enum ConversationContextAddedBy { - user - assistant -} - -"""A connection to a list of items.""" -type ConversationContextConnection { - """A list of edges.""" - edges: [ConversationContextEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" totalCount: Int! } - -"""An edge in a connection.""" -type ConversationContextEdge { - """The item at the end of the edge.""" - node: ConversationContext - - """A cursor for use in pagination.""" +""" +An edge in a connection. +""" +type ConversationEdge { + """ + The item at the end of the edge. + """ + node: Conversation + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""ConversationContextEntityType is enum for the field entity_type""" -enum ConversationContextEntityType { - service - log_event -} - -"""Ordering options for ConversationContext connections""" -input ConversationContextOrder { - """The ordering direction.""" +""" +Ordering options for Conversation connections +""" +input ConversationOrder { + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order ConversationContexts.""" - field: ConversationContextOrderField! + """ + The field by which to order Conversations. + """ + field: ConversationOrderField! } - -"""Properties by which ConversationContext connections can be ordered.""" -enum ConversationContextOrderField { +""" +Properties by which Conversation connections can be ordered. +""" +enum ConversationOrderField { CREATED_AT + UPDATED_AT } - """ -ConversationContextWhereInput is used for filtering ConversationContext objects. +ConversationWhereInput is used for filtering Conversation objects. Input was generated by ent. """ -input ConversationContextWhereInput { - not: ConversationContextWhereInput - and: [ConversationContextWhereInput!] - or: [ConversationContextWhereInput!] - - """id field predicates""" +input ConversationWhereInput { + not: ConversationWhereInput + and: [ConversationWhereInput!] + or: [ConversationWhereInput!] + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -398,128 +341,16 @@ input ConversationContextWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """conversation_id field predicates""" - conversationID: ID - conversationIDNEQ: ID - conversationIDIn: [ID!] - conversationIDNotIn: [ID!] - - """entity_type field predicates""" - entityType: ConversationContextEntityType - entityTypeNEQ: ConversationContextEntityType - entityTypeIn: [ConversationContextEntityType!] - entityTypeNotIn: [ConversationContextEntityType!] - - """entity_id field predicates""" - entityID: UUID - entityIDNEQ: UUID - entityIDIn: [UUID!] - entityIDNotIn: [UUID!] - entityIDGT: UUID - entityIDGTE: UUID - entityIDLT: UUID - entityIDLTE: UUID - - """added_by field predicates""" - addedBy: ConversationContextAddedBy - addedByNEQ: ConversationContextAddedBy - addedByIn: [ConversationContextAddedBy!] - addedByNotIn: [ConversationContextAddedBy!] - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """conversation edge predicates""" - hasConversation: Boolean - hasConversationWith: [ConversationWhereInput!] -} - -"""An edge in a connection.""" -type ConversationEdge { - """The item at the end of the edge.""" - node: Conversation - - """A cursor for use in pagination.""" - cursor: Cursor! -} - -"""Ordering options for Conversation connections""" -input ConversationOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order Conversations.""" - field: ConversationOrderField! -} - -"""Properties by which Conversation connections can be ordered.""" -enum ConversationOrderField { - CREATED_AT - UPDATED_AT -} - -""" -ConversationWhereInput is used for filtering Conversation objects. -Input was generated by ent. -""" -input ConversationWhereInput { - not: ConversationWhereInput - and: [ConversationWhereInput!] - or: [ConversationWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """workspace_id field predicates""" - workspaceID: ID - workspaceIDNEQ: ID - workspaceIDIn: [ID!] - workspaceIDNotIn: [ID!] - - """view_id field predicates""" - viewID: ID - viewIDNEQ: ID - viewIDIn: [ID!] - viewIDNotIn: [ID!] - viewIDIsNil: Boolean - viewIDNotNil: Boolean - - """user_id field predicates""" + """ + account_id field predicates + """ + accountID: ID + accountIDNEQ: ID + accountIDIn: [ID!] + accountIDNotIn: [ID!] + """ + user_id field predicates + """ userID: String userIDNEQ: String userIDIn: [String!] @@ -533,8 +364,9 @@ input ConversationWhereInput { userIDHasSuffix: String userIDEqualFold: String userIDContainsFold: String - - """title field predicates""" + """ + title field predicates + """ title: String titleNEQ: String titleIn: [String!] @@ -550,8 +382,9 @@ input ConversationWhereInput { titleNotNil: Boolean titleEqualFold: String titleContainsFold: String - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -560,8 +393,9 @@ input ConversationWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -570,339 +404,214 @@ input ConversationWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """workspace edge predicates""" - hasWorkspace: Boolean - hasWorkspaceWith: [WorkspaceWhereInput!] - - """messages edge predicates""" + """ + account edge predicates + """ + hasAccount: Boolean + hasAccountWith: [AccountWhereInput!] + """ + messages edge predicates + """ hasMessages: Boolean hasMessagesWith: [MessageWhereInput!] - - """conversation_contexts edge predicates""" - hasConversationContexts: Boolean - hasConversationContextsWith: [ConversationContextWhereInput!] - - """views edge predicates""" - hasViews: Boolean - hasViewsWith: [ViewWhereInput!] - - """view edge predicates""" - hasView: Boolean - hasViewWith: [ViewWhereInput!] } - """ CreateAccountInput is used for create Account object. Input was generated by ent. """ input CreateAccountInput { - """Human-readable name within the organization""" + """ + Human-readable name within the organization + """ name: String! organizationID: ID! datadogAccountID: ID - - """ - Optional client-provided UUID for offline-first sync. - If provided and a record with this ID exists, returns the existing record. - """ - id: ID } - """ CreateConversationInput is used for create Conversation object. Input was generated by ent. """ input CreateConversationInput { - """AI-generated title, set after first exchange""" - title: String - workspaceID: ID! - viewID: ID - """ - Optional client-provided UUID for offline-first sync. - If provided and a conversation with this ID exists, returns the existing record. + AI-generated title, set after first exchange """ - id: ID + title: String } - """ CreateDatadogAccountInput is used for create DatadogAccount object. Input was generated by ent. """ input CreateDatadogAccountInput { - """Display name for this Datadog account""" + """ + Display name for this Datadog account + """ name: String! - """ - Datadog regional site. US1: datadoghq.com, US3: us3.datadoghq.com, US5: - us5.datadoghq.com, EU1: datadoghq.eu, US1_FED: ddog-gov.com, AP1: - ap1.datadoghq.com, AP2: ap2.datadoghq.com. + Datadog regional site. Values: US1 = datadoghq.com.; US3 = us3.datadoghq.com.; US5 = us5.datadoghq.com.; EU1 = datadoghq.eu.; US1_FED = ddog-gov.com.; AP1 = ap1.datadoghq.com.; AP2 = ap2.datadoghq.com. """ site: DatadogAccountSite! - """ - Cost per GB of log data ingested (USD). NULL = using Datadog's published rate - ($0.10/GB). Set to override with actual contract rate. + Cost per GB of log data ingested (USD). NULL = using Datadog's published rate ($0.10/GB). Set to override with actual contract rate. """ costPerGBIngested: Float - """ - Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = - 80%). Leaves headroom for the customer's own API usage. + Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = 80%). Leaves headroom for the customer's own API usage. """ rateLimitUtilization: Float accountID: ID! - - """ - Optional client-provided UUID for offline-first sync. - If provided and a record with this ID exists, returns the existing record. - """ - id: ID -} - -input CreateDatadogAccountWithCredentialsInput { - attributes: CreateDatadogAccountInput! - credentials: CreateDatadogCredentialsInput! -} - -input CreateDatadogCredentialsInput { - apiKey: String! - appKey: String! -} - -input CreateEdgeApiKeyInput { - """ - Optional client-provided ID for idempotent creates. - If provided and already exists, returns the existing key (without plain key). - """ - id: ID - - """The account this API key authenticates to""" - accountID: ID! - - """User-provided name for this key (e.g., 'Production Collector')""" - name: String! -} - -type CreateEdgeApiKeyResult { - """The created API key record (without the secret)""" - edgeApiKey: EdgeApiKey! - - """ - The plain API key - only returned once at creation time. - Store this securely, it cannot be retrieved again. - """ - plainKey: String! } - """ CreateMessageInput is used for create Message object. Input was generated by ent. """ input CreateMessageInput { """ - Who sent this message. user: human-originated, assistant: AI-originated. + Who sent this message. Values: user = Human-originated message content.; assistant = AI-originated message content. """ role: MessageRole! - """ - Why the assistant stopped generating. end_turn: completed response, tool_use: - paused to call a tool. Null for user messages. + Why the assistant stopped generating. Null for user messages. Values: end_turn = The assistant completed its response.; tool_use = The assistant paused to call a tool. """ stopReason: MessageStopReason - - """AI model that produced this message. Null for user messages.""" - model: String - conversationID: ID! - """ - Optional client-provided UUID for offline-first sync. - If provided and a message with this ID exists, returns the existing record. + AI model that produced this message. Null for user messages. """ - id: ID - - """Array of typed content blocks.""" - content: [ContentBlockInput!]! + model: String + conversationID: ID! } - """ CreateOrganizationInput is used for create Organization object. Input was generated by ent. """ input CreateOrganizationInput { - """Human-readable name, unique across the system""" - name: String! - """ - Optional client-provided UUID for offline-first sync. - If provided and a record with this ID exists, returns the existing record. + Human-readable name, unique across the system """ - id: ID + name: String! } - """ CreateTeamInput is used for create Team object. Input was generated by ent. """ input CreateTeamInput { - """Human-readable name within the workspace""" - name: String! - workspaceID: ID! - - """ - Optional client-provided UUID for offline-first sync. - If provided and a record with this ID exists, returns the existing record. - """ - id: ID -} - -""" -CreateViewFavoriteInput is used for create ViewFavorite object. -Input was generated by ent. -""" -input CreateViewFavoriteInput { - viewID: ID! - """ - Optional client-provided UUID for offline-first sync. - If provided and a favorite with this ID exists, returns the existing record. + Human-readable team name within the organization """ - id: ID -} - -""" -CreateViewInput is used for create View object. -Input was generated by ent. -""" -input CreateViewInput { + name: String! """ - Which catalog entity this view queries. service: applications, log_event: event patterns, policy: quality recommendations. + Optional description of the team's scope and responsibilities """ - entityType: ViewEntityType! - - """Raw SQL query executed against the client's local SQLite database""" - query: String! - messageID: ID! - conversationID: ID! - forkedFromID: ID - forkIDs: [ID!] - + description: String """ - Optional client-provided UUID for offline-first sync. - If provided and a view with this ID exists, returns the existing record. + Optional external group identifier reserved for future SCIM mapping """ - id: ID + externalID: String + organizationID: ID! } - """ CreateWorkspaceInput is used for create Workspace object. Input was generated by ent. """ input CreateWorkspaceInput { - """Human-readable name within the account""" + """ + Human-readable name within the account + """ name: String! - """ - Primary purpose determining evaluation strategy. observability: performance - and reliability, security: threat detection, compliance: regulatory requirements. + Primary purpose determining evaluation strategy. Values: observability = Performance, reliability, and operational visibility.; security = Threat detection, investigation, and security posture.; compliance = Regulatory, privacy, and policy compliance review. """ purpose: WorkspacePurpose accountID: ID! - - """ - Optional client-provided UUID for offline-first sync. - If provided and a record with this ID exists, returns the existing record. - """ - id: ID } - """ Define a Relay Cursor type: https://relay.dev/graphql/connections.htm#sec-Cursor """ scalar Cursor - type DatadogAccount implements Node { - """Unique identifier of the Datadog configuration""" + """ + Unique identifier of the Datadog configuration + """ id: ID! - - """Parent account this configuration belongs to""" + """ + Parent account this configuration belongs to + """ accountID: ID! - - """Display name for this Datadog account""" + """ + Display name for this Datadog account + """ name: String! - """ - Datadog regional site. US1: datadoghq.com, US3: us3.datadoghq.com, US5: - us5.datadoghq.com, EU1: datadoghq.eu, US1_FED: ddog-gov.com, AP1: - ap1.datadoghq.com, AP2: ap2.datadoghq.com. + Datadog regional site. Values: US1 = datadoghq.com.; US3 = us3.datadoghq.com.; US5 = us5.datadoghq.com.; EU1 = datadoghq.eu.; US1_FED = ddog-gov.com.; AP1 = ap1.datadoghq.com.; AP2 = ap2.datadoghq.com. """ site: DatadogAccountSite! - - """When the Datadog account was created""" + """ + When the Datadog account was created + """ createdAt: Time! - - """When the Datadog account was last updated""" + """ + When the Datadog account was last updated + """ updatedAt: Time! - """ - Cost per GB of log data ingested (USD). NULL = using Datadog's published rate - ($0.10/GB). Set to override with actual contract rate. + Cost per GB of log data ingested (USD). NULL = using Datadog's published rate ($0.10/GB). Set to override with actual contract rate. """ - costPerGBIngested: Float - + costPerGBIngested: Float @goField(name: "CostPerGBIngested", forceResolver: false) """ - Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = - 80%). Leaves headroom for the customer's own API usage. + Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = 80%). Leaves headroom for the customer's own API usage. """ rateLimitUtilization: Float - - """Account this Datadog configuration belongs to""" - account: Account! - - """Discovered log indexes in this Datadog account""" - logIndexes: [DatadogLogIndex!] - """ - Status of this Datadog account in the discovery pipeline. - Derived from the status of all services discovered from this account. - Returns null if cache has not been populated yet. + Account this Datadog configuration belongs to """ - status: DatadogAccountStatusCache + account: Account! } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type DatadogAccountConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [DatadogAccountEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type DatadogAccountEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: DatadogAccount - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for DatadogAccount connections""" +""" +Ordering options for DatadogAccount connections +""" input DatadogAccountOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order DatadogAccounts.""" + """ + The field by which to order DatadogAccounts. + """ field: DatadogAccountOrderField! } - -"""Properties by which DatadogAccount connections can be ordered.""" +""" +Properties by which DatadogAccount connections can be ordered. +""" enum DatadogAccountOrderField { NAME SITE @@ -910,9 +619,10 @@ enum DatadogAccountOrderField { UPDATED_AT COST_PER_GB_INGESTED } - -"""DatadogAccountSite is enum for the field site""" -enum DatadogAccountSite { +""" +DatadogAccountSite is enum for the field site +""" +enum DatadogAccountSite @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/datadogaccount.Site") { US1 US3 US5 @@ -921,74 +631,17 @@ enum DatadogAccountSite { AP1 AP2 } - -type DatadogAccountStatusCache implements Node { - id: ID! - datadogAccountID: ID! - accountID: UUID! - - """ - Overall health of the Datadog account. DISABLED (integration turned off), INACTIVE (no data received), OK (healthy). - """ - health: DatadogAccountStatusCacheHealth! - readyForUse: Boolean! - logEventAnalyzedCount: Int! - logServiceCount: Int! - logActiveServices: Int! - disabledServices: Int! - inactiveServices: Int! - okServices: Int! - logEventCount: Int! - policyPendingCount: Int! - policyApprovedCount: Int! - policyDismissedCount: Int! - policyPendingLowCount: Int! - policyPendingMediumCount: Int! - policyPendingHighCount: Int! - policyPendingCriticalCount: Int! - serviceVolumePerHour: Float - serviceCostPerHourVolumeUsd: Float - logEventVolumePerHour: Float - logEventBytesPerHour: Float - logEventCostPerHourBytesUsd: Float - logEventCostPerHourVolumeUsd: Float - logEventCostPerHourUsd: Float - estimatedVolumeReductionPerHour: Float - estimatedBytesReductionPerHour: Float - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourUsd: Float - observedVolumePerHourBefore: Float - observedVolumePerHourAfter: Float - observedBytesPerHourBefore: Float - observedBytesPerHourAfter: Float - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeUsd: Float - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterUsd: Float - refreshedAt: Time! -} - -"""DatadogAccountStatusCacheHealth is enum for the field health""" -enum DatadogAccountStatusCacheHealth { - DISABLED - INACTIVE - ERROR - OK -} - """ -DatadogAccountStatusCacheWhereInput is used for filtering DatadogAccountStatusCache objects. +DatadogAccountWhereInput is used for filtering DatadogAccount objects. Input was generated by ent. """ -input DatadogAccountStatusCacheWhereInput { - not: DatadogAccountStatusCacheWhereInput - and: [DatadogAccountStatusCacheWhereInput!] - or: [DatadogAccountStatusCacheWhereInput!] - - """id field predicates""" +input DatadogAccountWhereInput { + not: DatadogAccountWhereInput + and: [DatadogAccountWhereInput!] + or: [DatadogAccountWhereInput!] + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -997,474 +650,16 @@ input DatadogAccountStatusCacheWhereInput { idGTE: ID idLT: ID idLTE: ID - - """datadog_account_id field predicates""" - datadogAccountID: ID - datadogAccountIDNEQ: ID - datadogAccountIDIn: [ID!] - datadogAccountIDNotIn: [ID!] - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """health field predicates""" - health: DatadogAccountStatusCacheHealth - healthNEQ: DatadogAccountStatusCacheHealth - healthIn: [DatadogAccountStatusCacheHealth!] - healthNotIn: [DatadogAccountStatusCacheHealth!] - - """ready_for_use field predicates""" - readyForUse: Boolean - readyForUseNEQ: Boolean - - """log_event_analyzed_count field predicates""" - logEventAnalyzedCount: Int - logEventAnalyzedCountNEQ: Int - logEventAnalyzedCountIn: [Int!] - logEventAnalyzedCountNotIn: [Int!] - logEventAnalyzedCountGT: Int - logEventAnalyzedCountGTE: Int - logEventAnalyzedCountLT: Int - logEventAnalyzedCountLTE: Int - - """log_service_count field predicates""" - logServiceCount: Int - logServiceCountNEQ: Int - logServiceCountIn: [Int!] - logServiceCountNotIn: [Int!] - logServiceCountGT: Int - logServiceCountGTE: Int - logServiceCountLT: Int - logServiceCountLTE: Int - - """log_active_services field predicates""" - logActiveServices: Int - logActiveServicesNEQ: Int - logActiveServicesIn: [Int!] - logActiveServicesNotIn: [Int!] - logActiveServicesGT: Int - logActiveServicesGTE: Int - logActiveServicesLT: Int - logActiveServicesLTE: Int - - """disabled_services field predicates""" - disabledServices: Int - disabledServicesNEQ: Int - disabledServicesIn: [Int!] - disabledServicesNotIn: [Int!] - disabledServicesGT: Int - disabledServicesGTE: Int - disabledServicesLT: Int - disabledServicesLTE: Int - - """inactive_services field predicates""" - inactiveServices: Int - inactiveServicesNEQ: Int - inactiveServicesIn: [Int!] - inactiveServicesNotIn: [Int!] - inactiveServicesGT: Int - inactiveServicesGTE: Int - inactiveServicesLT: Int - inactiveServicesLTE: Int - - """ok_services field predicates""" - okServices: Int - okServicesNEQ: Int - okServicesIn: [Int!] - okServicesNotIn: [Int!] - okServicesGT: Int - okServicesGTE: Int - okServicesLT: Int - okServicesLTE: Int - - """log_event_count field predicates""" - logEventCount: Int - logEventCountNEQ: Int - logEventCountIn: [Int!] - logEventCountNotIn: [Int!] - logEventCountGT: Int - logEventCountGTE: Int - logEventCountLT: Int - logEventCountLTE: Int - - """policy_pending_count field predicates""" - policyPendingCount: Int - policyPendingCountNEQ: Int - policyPendingCountIn: [Int!] - policyPendingCountNotIn: [Int!] - policyPendingCountGT: Int - policyPendingCountGTE: Int - policyPendingCountLT: Int - policyPendingCountLTE: Int - - """policy_approved_count field predicates""" - policyApprovedCount: Int - policyApprovedCountNEQ: Int - policyApprovedCountIn: [Int!] - policyApprovedCountNotIn: [Int!] - policyApprovedCountGT: Int - policyApprovedCountGTE: Int - policyApprovedCountLT: Int - policyApprovedCountLTE: Int - - """policy_dismissed_count field predicates""" - policyDismissedCount: Int - policyDismissedCountNEQ: Int - policyDismissedCountIn: [Int!] - policyDismissedCountNotIn: [Int!] - policyDismissedCountGT: Int - policyDismissedCountGTE: Int - policyDismissedCountLT: Int - policyDismissedCountLTE: Int - - """policy_pending_low_count field predicates""" - policyPendingLowCount: Int - policyPendingLowCountNEQ: Int - policyPendingLowCountIn: [Int!] - policyPendingLowCountNotIn: [Int!] - policyPendingLowCountGT: Int - policyPendingLowCountGTE: Int - policyPendingLowCountLT: Int - policyPendingLowCountLTE: Int - - """policy_pending_medium_count field predicates""" - policyPendingMediumCount: Int - policyPendingMediumCountNEQ: Int - policyPendingMediumCountIn: [Int!] - policyPendingMediumCountNotIn: [Int!] - policyPendingMediumCountGT: Int - policyPendingMediumCountGTE: Int - policyPendingMediumCountLT: Int - policyPendingMediumCountLTE: Int - - """policy_pending_high_count field predicates""" - policyPendingHighCount: Int - policyPendingHighCountNEQ: Int - policyPendingHighCountIn: [Int!] - policyPendingHighCountNotIn: [Int!] - policyPendingHighCountGT: Int - policyPendingHighCountGTE: Int - policyPendingHighCountLT: Int - policyPendingHighCountLTE: Int - - """policy_pending_critical_count field predicates""" - policyPendingCriticalCount: Int - policyPendingCriticalCountNEQ: Int - policyPendingCriticalCountIn: [Int!] - policyPendingCriticalCountNotIn: [Int!] - policyPendingCriticalCountGT: Int - policyPendingCriticalCountGTE: Int - policyPendingCriticalCountLT: Int - policyPendingCriticalCountLTE: Int - - """service_volume_per_hour field predicates""" - serviceVolumePerHour: Float - serviceVolumePerHourNEQ: Float - serviceVolumePerHourIn: [Float!] - serviceVolumePerHourNotIn: [Float!] - serviceVolumePerHourGT: Float - serviceVolumePerHourGTE: Float - serviceVolumePerHourLT: Float - serviceVolumePerHourLTE: Float - serviceVolumePerHourIsNil: Boolean - serviceVolumePerHourNotNil: Boolean - - """service_cost_per_hour_volume_usd field predicates""" - serviceCostPerHourVolumeUsd: Float - serviceCostPerHourVolumeUsdNEQ: Float - serviceCostPerHourVolumeUsdIn: [Float!] - serviceCostPerHourVolumeUsdNotIn: [Float!] - serviceCostPerHourVolumeUsdGT: Float - serviceCostPerHourVolumeUsdGTE: Float - serviceCostPerHourVolumeUsdLT: Float - serviceCostPerHourVolumeUsdLTE: Float - serviceCostPerHourVolumeUsdIsNil: Boolean - serviceCostPerHourVolumeUsdNotNil: Boolean - - """log_event_volume_per_hour field predicates""" - logEventVolumePerHour: Float - logEventVolumePerHourNEQ: Float - logEventVolumePerHourIn: [Float!] - logEventVolumePerHourNotIn: [Float!] - logEventVolumePerHourGT: Float - logEventVolumePerHourGTE: Float - logEventVolumePerHourLT: Float - logEventVolumePerHourLTE: Float - logEventVolumePerHourIsNil: Boolean - logEventVolumePerHourNotNil: Boolean - - """log_event_bytes_per_hour field predicates""" - logEventBytesPerHour: Float - logEventBytesPerHourNEQ: Float - logEventBytesPerHourIn: [Float!] - logEventBytesPerHourNotIn: [Float!] - logEventBytesPerHourGT: Float - logEventBytesPerHourGTE: Float - logEventBytesPerHourLT: Float - logEventBytesPerHourLTE: Float - logEventBytesPerHourIsNil: Boolean - logEventBytesPerHourNotNil: Boolean - - """log_event_cost_per_hour_bytes_usd field predicates""" - logEventCostPerHourBytesUsd: Float - logEventCostPerHourBytesUsdNEQ: Float - logEventCostPerHourBytesUsdIn: [Float!] - logEventCostPerHourBytesUsdNotIn: [Float!] - logEventCostPerHourBytesUsdGT: Float - logEventCostPerHourBytesUsdGTE: Float - logEventCostPerHourBytesUsdLT: Float - logEventCostPerHourBytesUsdLTE: Float - logEventCostPerHourBytesUsdIsNil: Boolean - logEventCostPerHourBytesUsdNotNil: Boolean - - """log_event_cost_per_hour_volume_usd field predicates""" - logEventCostPerHourVolumeUsd: Float - logEventCostPerHourVolumeUsdNEQ: Float - logEventCostPerHourVolumeUsdIn: [Float!] - logEventCostPerHourVolumeUsdNotIn: [Float!] - logEventCostPerHourVolumeUsdGT: Float - logEventCostPerHourVolumeUsdGTE: Float - logEventCostPerHourVolumeUsdLT: Float - logEventCostPerHourVolumeUsdLTE: Float - logEventCostPerHourVolumeUsdIsNil: Boolean - logEventCostPerHourVolumeUsdNotNil: Boolean - - """log_event_cost_per_hour_usd field predicates""" - logEventCostPerHourUsd: Float - logEventCostPerHourUsdNEQ: Float - logEventCostPerHourUsdIn: [Float!] - logEventCostPerHourUsdNotIn: [Float!] - logEventCostPerHourUsdGT: Float - logEventCostPerHourUsdGTE: Float - logEventCostPerHourUsdLT: Float - logEventCostPerHourUsdLTE: Float - logEventCostPerHourUsdIsNil: Boolean - logEventCostPerHourUsdNotNil: Boolean - - """estimated_volume_reduction_per_hour field predicates""" - estimatedVolumeReductionPerHour: Float - estimatedVolumeReductionPerHourNEQ: Float - estimatedVolumeReductionPerHourIn: [Float!] - estimatedVolumeReductionPerHourNotIn: [Float!] - estimatedVolumeReductionPerHourGT: Float - estimatedVolumeReductionPerHourGTE: Float - estimatedVolumeReductionPerHourLT: Float - estimatedVolumeReductionPerHourLTE: Float - estimatedVolumeReductionPerHourIsNil: Boolean - estimatedVolumeReductionPerHourNotNil: Boolean - - """estimated_bytes_reduction_per_hour field predicates""" - estimatedBytesReductionPerHour: Float - estimatedBytesReductionPerHourNEQ: Float - estimatedBytesReductionPerHourIn: [Float!] - estimatedBytesReductionPerHourNotIn: [Float!] - estimatedBytesReductionPerHourGT: Float - estimatedBytesReductionPerHourGTE: Float - estimatedBytesReductionPerHourLT: Float - estimatedBytesReductionPerHourLTE: Float - estimatedBytesReductionPerHourIsNil: Boolean - estimatedBytesReductionPerHourNotNil: Boolean - - """estimated_cost_reduction_per_hour_bytes_usd field predicates""" - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourBytesUsdNEQ: Float - estimatedCostReductionPerHourBytesUsdIn: [Float!] - estimatedCostReductionPerHourBytesUsdNotIn: [Float!] - estimatedCostReductionPerHourBytesUsdGT: Float - estimatedCostReductionPerHourBytesUsdGTE: Float - estimatedCostReductionPerHourBytesUsdLT: Float - estimatedCostReductionPerHourBytesUsdLTE: Float - estimatedCostReductionPerHourBytesUsdIsNil: Boolean - estimatedCostReductionPerHourBytesUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_volume_usd field predicates""" - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourVolumeUsdNEQ: Float - estimatedCostReductionPerHourVolumeUsdIn: [Float!] - estimatedCostReductionPerHourVolumeUsdNotIn: [Float!] - estimatedCostReductionPerHourVolumeUsdGT: Float - estimatedCostReductionPerHourVolumeUsdGTE: Float - estimatedCostReductionPerHourVolumeUsdLT: Float - estimatedCostReductionPerHourVolumeUsdLTE: Float - estimatedCostReductionPerHourVolumeUsdIsNil: Boolean - estimatedCostReductionPerHourVolumeUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_usd field predicates""" - estimatedCostReductionPerHourUsd: Float - estimatedCostReductionPerHourUsdNEQ: Float - estimatedCostReductionPerHourUsdIn: [Float!] - estimatedCostReductionPerHourUsdNotIn: [Float!] - estimatedCostReductionPerHourUsdGT: Float - estimatedCostReductionPerHourUsdGTE: Float - estimatedCostReductionPerHourUsdLT: Float - estimatedCostReductionPerHourUsdLTE: Float - estimatedCostReductionPerHourUsdIsNil: Boolean - estimatedCostReductionPerHourUsdNotNil: Boolean - - """observed_volume_per_hour_before field predicates""" - observedVolumePerHourBefore: Float - observedVolumePerHourBeforeNEQ: Float - observedVolumePerHourBeforeIn: [Float!] - observedVolumePerHourBeforeNotIn: [Float!] - observedVolumePerHourBeforeGT: Float - observedVolumePerHourBeforeGTE: Float - observedVolumePerHourBeforeLT: Float - observedVolumePerHourBeforeLTE: Float - observedVolumePerHourBeforeIsNil: Boolean - observedVolumePerHourBeforeNotNil: Boolean - - """observed_volume_per_hour_after field predicates""" - observedVolumePerHourAfter: Float - observedVolumePerHourAfterNEQ: Float - observedVolumePerHourAfterIn: [Float!] - observedVolumePerHourAfterNotIn: [Float!] - observedVolumePerHourAfterGT: Float - observedVolumePerHourAfterGTE: Float - observedVolumePerHourAfterLT: Float - observedVolumePerHourAfterLTE: Float - observedVolumePerHourAfterIsNil: Boolean - observedVolumePerHourAfterNotNil: Boolean - - """observed_bytes_per_hour_before field predicates""" - observedBytesPerHourBefore: Float - observedBytesPerHourBeforeNEQ: Float - observedBytesPerHourBeforeIn: [Float!] - observedBytesPerHourBeforeNotIn: [Float!] - observedBytesPerHourBeforeGT: Float - observedBytesPerHourBeforeGTE: Float - observedBytesPerHourBeforeLT: Float - observedBytesPerHourBeforeLTE: Float - observedBytesPerHourBeforeIsNil: Boolean - observedBytesPerHourBeforeNotNil: Boolean - - """observed_bytes_per_hour_after field predicates""" - observedBytesPerHourAfter: Float - observedBytesPerHourAfterNEQ: Float - observedBytesPerHourAfterIn: [Float!] - observedBytesPerHourAfterNotIn: [Float!] - observedBytesPerHourAfterGT: Float - observedBytesPerHourAfterGTE: Float - observedBytesPerHourAfterLT: Float - observedBytesPerHourAfterLTE: Float - observedBytesPerHourAfterIsNil: Boolean - observedBytesPerHourAfterNotNil: Boolean - - """observed_cost_per_hour_before_bytes_usd field predicates""" - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeBytesUsdNEQ: Float - observedCostPerHourBeforeBytesUsdIn: [Float!] - observedCostPerHourBeforeBytesUsdNotIn: [Float!] - observedCostPerHourBeforeBytesUsdGT: Float - observedCostPerHourBeforeBytesUsdGTE: Float - observedCostPerHourBeforeBytesUsdLT: Float - observedCostPerHourBeforeBytesUsdLTE: Float - observedCostPerHourBeforeBytesUsdIsNil: Boolean - observedCostPerHourBeforeBytesUsdNotNil: Boolean - - """observed_cost_per_hour_before_volume_usd field predicates""" - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeVolumeUsdNEQ: Float - observedCostPerHourBeforeVolumeUsdIn: [Float!] - observedCostPerHourBeforeVolumeUsdNotIn: [Float!] - observedCostPerHourBeforeVolumeUsdGT: Float - observedCostPerHourBeforeVolumeUsdGTE: Float - observedCostPerHourBeforeVolumeUsdLT: Float - observedCostPerHourBeforeVolumeUsdLTE: Float - observedCostPerHourBeforeVolumeUsdIsNil: Boolean - observedCostPerHourBeforeVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_before_usd field predicates""" - observedCostPerHourBeforeUsd: Float - observedCostPerHourBeforeUsdNEQ: Float - observedCostPerHourBeforeUsdIn: [Float!] - observedCostPerHourBeforeUsdNotIn: [Float!] - observedCostPerHourBeforeUsdGT: Float - observedCostPerHourBeforeUsdGTE: Float - observedCostPerHourBeforeUsdLT: Float - observedCostPerHourBeforeUsdLTE: Float - observedCostPerHourBeforeUsdIsNil: Boolean - observedCostPerHourBeforeUsdNotNil: Boolean - - """observed_cost_per_hour_after_bytes_usd field predicates""" - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterBytesUsdNEQ: Float - observedCostPerHourAfterBytesUsdIn: [Float!] - observedCostPerHourAfterBytesUsdNotIn: [Float!] - observedCostPerHourAfterBytesUsdGT: Float - observedCostPerHourAfterBytesUsdGTE: Float - observedCostPerHourAfterBytesUsdLT: Float - observedCostPerHourAfterBytesUsdLTE: Float - observedCostPerHourAfterBytesUsdIsNil: Boolean - observedCostPerHourAfterBytesUsdNotNil: Boolean - - """observed_cost_per_hour_after_volume_usd field predicates""" - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterVolumeUsdNEQ: Float - observedCostPerHourAfterVolumeUsdIn: [Float!] - observedCostPerHourAfterVolumeUsdNotIn: [Float!] - observedCostPerHourAfterVolumeUsdGT: Float - observedCostPerHourAfterVolumeUsdGTE: Float - observedCostPerHourAfterVolumeUsdLT: Float - observedCostPerHourAfterVolumeUsdLTE: Float - observedCostPerHourAfterVolumeUsdIsNil: Boolean - observedCostPerHourAfterVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_after_usd field predicates""" - observedCostPerHourAfterUsd: Float - observedCostPerHourAfterUsdNEQ: Float - observedCostPerHourAfterUsdIn: [Float!] - observedCostPerHourAfterUsdNotIn: [Float!] - observedCostPerHourAfterUsdGT: Float - observedCostPerHourAfterUsdGTE: Float - observedCostPerHourAfterUsdLT: Float - observedCostPerHourAfterUsdLTE: Float - observedCostPerHourAfterUsdIsNil: Boolean - observedCostPerHourAfterUsdNotNil: Boolean - - """refreshed_at field predicates""" - refreshedAt: Time - refreshedAtNEQ: Time - refreshedAtIn: [Time!] - refreshedAtNotIn: [Time!] - refreshedAtGT: Time - refreshedAtGTE: Time - refreshedAtLT: Time - refreshedAtLTE: Time -} - -""" -DatadogAccountWhereInput is used for filtering DatadogAccount objects. -Input was generated by ent. -""" -input DatadogAccountWhereInput { - not: DatadogAccountWhereInput - and: [DatadogAccountWhereInput!] - or: [DatadogAccountWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: ID accountIDNEQ: ID accountIDIn: [ID!] accountIDNotIn: [ID!] - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -1478,14 +673,16 @@ input DatadogAccountWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """site field predicates""" + """ + site field predicates + """ site: DatadogAccountSite siteNEQ: DatadogAccountSite siteIn: [DatadogAccountSite!] siteNotIn: [DatadogAccountSite!] - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -1494,8 +691,9 @@ input DatadogAccountWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -1504,8 +702,9 @@ input DatadogAccountWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """cost_per_gb_ingested field predicates""" + """ + cost_per_gb_ingested field predicates + """ costPerGBIngested: Float costPerGBIngestedNEQ: Float costPerGBIngestedIn: [Float!] @@ -1516,8 +715,9 @@ input DatadogAccountWhereInput { costPerGBIngestedLTE: Float costPerGBIngestedIsNil: Boolean costPerGBIngestedNotNil: Boolean - - """rate_limit_utilization field predicates""" + """ + rate_limit_utilization field predicates + """ rateLimitUtilization: Float rateLimitUtilizationNEQ: Float rateLimitUtilizationIn: [Float!] @@ -1528,216 +728,98 @@ input DatadogAccountWhereInput { rateLimitUtilizationLTE: Float rateLimitUtilizationIsNil: Boolean rateLimitUtilizationNotNil: Boolean - - """account edge predicates""" + """ + account edge predicates + """ hasAccount: Boolean hasAccountWith: [AccountWhereInput!] - - """log_indexes edge predicates""" - hasLogIndexes: Boolean - hasLogIndexesWith: [DatadogLogIndexWhereInput!] } - -type DatadogLogIndex implements Node { - """Unique identifier for this index record""" +type EdgeApiKey implements Node { + """ + Unique identifier of this API key + """ id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from datadog_account.account_id. + The account this API key authenticates to """ - accountID: UUID! - - """The Datadog account this index belongs to""" - datadogAccountID: ID! - + accountID: ID! """ - Index name from Datadog (e.g., 'main', 'security', 'compliance') - this is the stable identifier + User-provided name for this key (e.g., 'Production Collector') """ name: String! - """ - Cost per million events indexed in this index (USD). NULL = using Datadog's - published rate ($1.70/M). SIEM indexes cost more — set accordingly. + First characters of the key for identification (e.g., 'tero_sk_abc1') """ - costPerMillionEventsIndexed: Float - - """When this index was first discovered""" - createdAt: Time! - - """Last time we saw logs flowing to this index""" - lastSeenAt: Time! - - """The Datadog account this index belongs to""" - datadogAccount: DatadogAccount! -} - -"""Ordering options for DatadogLogIndex connections""" -input DatadogLogIndexOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order DatadogLogIndexes.""" - field: DatadogLogIndexOrderField! -} - -"""Properties by which DatadogLogIndex connections can be ordered.""" -enum DatadogLogIndexOrderField { - NAME - CREATED_AT - LAST_SEEN_AT -} - -""" -DatadogLogIndexWhereInput is used for filtering DatadogLogIndex objects. -Input was generated by ent. -""" -input DatadogLogIndexWhereInput { - not: DatadogLogIndexWhereInput - and: [DatadogLogIndexWhereInput!] - or: [DatadogLogIndexWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """datadog_account_id field predicates""" - datadogAccountID: ID - datadogAccountIDNEQ: ID - datadogAccountIDIn: [ID!] - datadogAccountIDNotIn: [ID!] - - """name field predicates""" - name: String - nameNEQ: String - nameIn: [String!] - nameNotIn: [String!] - nameGT: String - nameGTE: String - nameLT: String - nameLTE: String - nameContains: String - nameHasPrefix: String - nameHasSuffix: String - nameEqualFold: String - nameContainsFold: String - - """cost_per_million_events_indexed field predicates""" - costPerMillionEventsIndexed: Float - costPerMillionEventsIndexedNEQ: Float - costPerMillionEventsIndexedIn: [Float!] - costPerMillionEventsIndexedNotIn: [Float!] - costPerMillionEventsIndexedGT: Float - costPerMillionEventsIndexedGTE: Float - costPerMillionEventsIndexedLT: Float - costPerMillionEventsIndexedLTE: Float - costPerMillionEventsIndexedIsNil: Boolean - costPerMillionEventsIndexedNotNil: Boolean - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """last_seen_at field predicates""" - lastSeenAt: Time - lastSeenAtNEQ: Time - lastSeenAtIn: [Time!] - lastSeenAtNotIn: [Time!] - lastSeenAtGT: Time - lastSeenAtGTE: Time - lastSeenAtLT: Time - lastSeenAtLTE: Time - - """datadog_account edge predicates""" - hasDatadogAccount: Boolean - hasDatadogAccountWith: [DatadogAccountWhereInput!] -} - -type EdgeApiKey implements Node { - """Unique identifier of this API key""" - id: ID! - - """The account this API key authenticates to""" - accountID: ID! - - """User-provided name for this key (e.g., 'Production Collector')""" - name: String! - - """First characters of the key for identification (e.g., 'tero_sk_abc1')""" keyPrefix: String! - - """When this key was last used for authentication""" + """ + When this key was last used for authentication + """ lastUsedAt: Time - - """When this key was revoked (null if active)""" + """ + When this key was revoked (null if active) + """ revokedAt: Time - - """When this key was created""" + """ + When this key was created + """ createdAt: Time! - - """The account this API key belongs to""" + """ + The account this API key belongs to + """ account: Account! } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type EdgeApiKeyConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [EdgeApiKeyEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type EdgeApiKeyEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: EdgeApiKey - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for EdgeApiKey connections""" +""" +Ordering options for EdgeApiKey connections +""" input EdgeApiKeyOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order EdgeApiKeys.""" + """ + The field by which to order EdgeApiKeys. + """ field: EdgeApiKeyOrderField! } - -"""Properties by which EdgeApiKey connections can be ordered.""" +""" +Properties by which EdgeApiKey connections can be ordered. +""" enum EdgeApiKeyOrderField { NAME LAST_USED_AT REVOKED_AT CREATED_AT } - """ EdgeApiKeyWhereInput is used for filtering EdgeApiKey objects. Input was generated by ent. @@ -1746,8 +828,9 @@ input EdgeApiKeyWhereInput { not: EdgeApiKeyWhereInput and: [EdgeApiKeyWhereInput!] or: [EdgeApiKeyWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -1756,14 +839,16 @@ input EdgeApiKeyWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: ID accountIDNEQ: ID accountIDIn: [ID!] accountIDNotIn: [ID!] - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -1777,8 +862,9 @@ input EdgeApiKeyWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """key_prefix field predicates""" + """ + key_prefix field predicates + """ keyPrefix: String keyPrefixNEQ: String keyPrefixIn: [String!] @@ -1792,8 +878,9 @@ input EdgeApiKeyWhereInput { keyPrefixHasSuffix: String keyPrefixEqualFold: String keyPrefixContainsFold: String - - """last_used_at field predicates""" + """ + last_used_at field predicates + """ lastUsedAt: Time lastUsedAtNEQ: Time lastUsedAtIn: [Time!] @@ -1804,8 +891,9 @@ input EdgeApiKeyWhereInput { lastUsedAtLTE: Time lastUsedAtIsNil: Boolean lastUsedAtNotNil: Boolean - - """revoked_at field predicates""" + """ + revoked_at field predicates + """ revokedAt: Time revokedAtNEQ: Time revokedAtIn: [Time!] @@ -1816,8 +904,9 @@ input EdgeApiKeyWhereInput { revokedAtLTE: Time revokedAtIsNil: Boolean revokedAtNotNil: Boolean - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -1826,78 +915,104 @@ input EdgeApiKeyWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """account edge predicates""" + """ + account edge predicates + """ hasAccount: Boolean hasAccountWith: [AccountWhereInput!] } - type EdgeInstance implements Node { - """Unique identifier of this edge instance""" + """ + Unique identifier of this edge instance + """ id: ID! - - """The account this edge instance belongs to""" + """ + The account this edge instance belongs to + """ accountID: ID! - - """The service.instance.id resource attribute identifying this instance""" + """ + The service.instance.id resource attribute identifying this instance + """ instanceID: String! - - """The service.name resource attribute""" + """ + The service.name resource attribute + """ serviceName: String! - - """The service.namespace resource attribute""" + """ + The service.namespace resource attribute + """ serviceNamespace: String - - """The service.version resource attribute""" + """ + The service.version resource attribute + """ serviceVersion: String - - """When this edge instance first synced""" + """ + When this edge instance first synced + """ firstSeenAt: Time! - - """When this edge instance last synced""" + """ + When this edge instance last synced + """ lastSyncAt: Time! - - """When this record was created""" + """ + When this record was created + """ createdAt: Time! - - """When this record was last updated""" + """ + When this record was last updated + """ updatedAt: Time! - - """The account this edge instance belongs to""" + """ + The account this edge instance belongs to + """ account: Account! } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type EdgeInstanceConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [EdgeInstanceEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type EdgeInstanceEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: EdgeInstance - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for EdgeInstance connections""" +""" +Ordering options for EdgeInstance connections +""" input EdgeInstanceOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order EdgeInstances.""" + """ + The field by which to order EdgeInstances. + """ field: EdgeInstanceOrderField! } - -"""Properties by which EdgeInstance connections can be ordered.""" +""" +Properties by which EdgeInstance connections can be ordered. +""" enum EdgeInstanceOrderField { INSTANCE_ID SERVICE_NAME @@ -1908,7 +1023,6 @@ enum EdgeInstanceOrderField { CREATED_AT UPDATED_AT } - """ EdgeInstanceWhereInput is used for filtering EdgeInstance objects. Input was generated by ent. @@ -1917,8 +1031,9 @@ input EdgeInstanceWhereInput { not: EdgeInstanceWhereInput and: [EdgeInstanceWhereInput!] or: [EdgeInstanceWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -1927,14 +1042,16 @@ input EdgeInstanceWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: ID accountIDNEQ: ID accountIDIn: [ID!] accountIDNotIn: [ID!] - - """instance_id field predicates""" + """ + instance_id field predicates + """ instanceID: String instanceIDNEQ: String instanceIDIn: [String!] @@ -1948,8 +1065,9 @@ input EdgeInstanceWhereInput { instanceIDHasSuffix: String instanceIDEqualFold: String instanceIDContainsFold: String - - """service_name field predicates""" + """ + service_name field predicates + """ serviceName: String serviceNameNEQ: String serviceNameIn: [String!] @@ -1963,8 +1081,9 @@ input EdgeInstanceWhereInput { serviceNameHasSuffix: String serviceNameEqualFold: String serviceNameContainsFold: String - - """service_namespace field predicates""" + """ + service_namespace field predicates + """ serviceNamespace: String serviceNamespaceNEQ: String serviceNamespaceIn: [String!] @@ -1980,8 +1099,9 @@ input EdgeInstanceWhereInput { serviceNamespaceNotNil: Boolean serviceNamespaceEqualFold: String serviceNamespaceContainsFold: String - - """service_version field predicates""" + """ + service_version field predicates + """ serviceVersion: String serviceVersionNEQ: String serviceVersionIn: [String!] @@ -1997,8 +1117,9 @@ input EdgeInstanceWhereInput { serviceVersionNotNil: Boolean serviceVersionEqualFold: String serviceVersionContainsFold: String - - """first_seen_at field predicates""" + """ + first_seen_at field predicates + """ firstSeenAt: Time firstSeenAtNEQ: Time firstSeenAtIn: [Time!] @@ -2007,8 +1128,9 @@ input EdgeInstanceWhereInput { firstSeenAtGTE: Time firstSeenAtLT: Time firstSeenAtLTE: Time - - """last_sync_at field predicates""" + """ + last_sync_at field predicates + """ lastSyncAt: Time lastSyncAtNEQ: Time lastSyncAtIn: [Time!] @@ -2017,8 +1139,9 @@ input EdgeInstanceWhereInput { lastSyncAtGTE: Time lastSyncAtLT: Time lastSyncAtLTE: Time - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -2027,8 +1150,9 @@ input EdgeInstanceWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -2037,165 +1161,204 @@ input EdgeInstanceWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """account edge predicates""" + """ + account edge predicates + """ hasAccount: Boolean hasAccountWith: [AccountWhereInput!] } - -type LogEvent implements Node { - """Unique identifier of the log event""" +type Finding implements Node { + """ + Unique identifier + """ id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from service.account_id. + Parent account this finding belongs to """ - accountID: UUID! - - """Service that produces this event""" - serviceID: ID! - - """Snake_case identifier unique per service, e.g. nginx_access_log""" - name: String! - + accountID: ID! """ - What the event is and what data instances carry. Helps engineers decide whether to look here. + Owning service for service-scoped or log-event-scoped findings. Null only for account-scoped findings. """ - description: String! - + serviceID: ID """ - Predominant log severity level, derived from example records. Nullable when - examples have no severity info. Values: debug, info, warn, error, other. + Associated log event when this finding is explicitly scoped to one log event. """ - severity: LogEventSeverity - + logEventID: ID """ - What role this event serves: diagnostic (investigate incidents), operational - (system behavior), lifecycle (state transitions), ephemeral (transient state). + Explicit scope discriminator for this finding. """ - signalPurpose: LogEventSignalPurpose! - + scopeKind: FindingScopeKind! """ - What this event records: system (internal mechanics), traffic (request flow), - activity (actor+action+resource), control (access/permission decisions). + Top-level product domain this finding belongs to, e.g. quality or operations. """ - eventNature: LogEventEventNature! - + domain: String! """ - True when this event represents a multi-line fragment rather than a complete log entry. Set by the classifier. + Problem type that raised this finding, e.g. reactive_flood. """ - isFragment: Boolean! - + type: String! """ - Sample log records captured during discovery, used for AI analysis and pattern validation + Version of the problem logic that most recently reconciled this finding. """ - examples: [LogRecord!]! - + problemVersion: Int! """ - Current trailing 7-day average events/hour. Refreshed on volume ingestion. + Stable problem-specific identity for this finding instance within an account. """ - baselineVolumePerHour: Float - + fingerprint: String! """ - Current trailing 7-day volume-weighted average bytes/event. Refreshed on volume ingestion. + When this finding stopped matching the current world. Null while active. + """ + closedAt: Time + """ + When this finding row was created. """ - baselineAvgBytes: Float - - """When the log event was created""" createdAt: Time! - - """When the log event was last updated""" + """ + When this finding row was last updated. + """ updatedAt: Time! - - """Service that produces this event""" - service: Service! - - """Log sample that produced this event during classification""" - logSample: LogSample! - - """Category-specific policies across workspaces""" - policies: [LogEventPolicy!] - - """Fields discovered in this event's example records""" - logEventFields: [LogEventField!] - """ - Status of this log event. - Shows where the log event is in the preparation pipeline. - Returns null if cache has not been populated yet. + Account this finding belongs to + """ + account: Account! + """ + Owning service for service-scoped or log-event-scoped findings + """ + service: Service + """ + Log event this finding is explicitly scoped to when the scope is one event + """ + logEvent: LogEvent + """ + Current AI curation for this finding. + """ + findingCuration: FindingCuration + """ + Versioned remediation plans associated with this finding. + """ + findingPlans: [FindingPlan!] + """ + Per-log-event policy contribution rows emitted from this finding. """ - status: LogEventStatusCache + logEventPolicies: [LogEventPolicy!] } - -"""A connection to a list of items.""" -type LogEventConnection { - """A list of edges.""" - edges: [LogEventEdge] - - """Information to aid in pagination.""" +""" +A connection to a list of items. +""" +type FindingConnection { + """ + A list of edges. + """ + edges: [FindingEdge] + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" -type LogEventEdge { - """The item at the end of the edge.""" - node: LogEvent - - """A cursor for use in pagination.""" - cursor: Cursor! -} - -"""LogEventEventNature is enum for the field event_nature""" -enum LogEventEventNature { - system - traffic - activity - control -} - -type LogEventField implements Node { - """Unique identifier""" +type FindingCuration implements Node { + """ + Unique identifier + """ id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. + Denormalized for tenant isolation. Auto-set via trigger from finding.account_id. """ accountID: UUID! - - """The log event this field belongs to""" - logEventID: ID! - - """Unambiguous path segments, e.g. {attributes, http, status}""" - fieldPath: [String!]! - """ - Current trailing 7-day volume-weighted average bytes for this attribute. Refreshed on volume ingestion. + The finding this curation belongs to + """ + findingID: ID! + """ + Version of the curation contract and prompt that produced this record. + """ + version: Int! + """ + Problem version of the finding when this curation was last produced. + """ + findingProblemVersion: Int! + """ + Whether this candidate should be kept as a legitimate finding or suppressed. Values: keep = Keep the finding as a legitimate user-facing issue.; suppress = Do not keep this candidate as a user-facing finding. + """ + disposition: FindingCurationDisposition! + """ + How much attention a kept finding deserves. Values: low = Legitimate finding, but low urgency or prominence.; medium = Legitimate finding with clear but not top-tier urgency.; high = Legitimate finding that deserves strong user attention. + """ + priority: FindingCurationPriority + """ + Short user-facing title for the curated finding. + """ + title: String! + """ + User-facing markdown-friendly explanation of the curated finding. + """ + body: String! + """ + When this curation row was created. """ - baselineAvgBytes: Float - - """When this field was last seen in production log samples.""" - lastSeenAt: Time! - - """When this field was first discovered""" createdAt: Time! - - """The log event this field belongs to""" - logEvent: LogEvent! + """ + When this curation row was last updated. + """ + updatedAt: Time! + """ + The finding this curation belongs to + """ + finding: Finding! +} +""" +FindingCurationDisposition is enum for the field disposition +""" +enum FindingCurationDisposition @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/findingcuration.Disposition") { + keep + suppress +} +""" +Ordering options for FindingCuration connections +""" +input FindingCurationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order FindingCurations. + """ + field: FindingCurationOrderField! +} +""" +Properties by which FindingCuration connections can be ordered. +""" +enum FindingCurationOrderField { + VERSION + FINDING_PROBLEM_VERSION + DISPOSITION + PRIORITY + TITLE + CREATED_AT + UPDATED_AT +} +""" +FindingCurationPriority is enum for the field priority +""" +enum FindingCurationPriority @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/findingcuration.Priority") { + low + medium + high } - """ -LogEventFieldWhereInput is used for filtering LogEventField objects. +FindingCurationWhereInput is used for filtering FindingCuration objects. Input was generated by ent. """ -input LogEventFieldWhereInput { - not: LogEventFieldWhereInput - and: [LogEventFieldWhereInput!] - or: [LogEventFieldWhereInput!] - - """id field predicates""" +input FindingCurationWhereInput { + not: FindingCurationWhereInput + and: [FindingCurationWhereInput!] + or: [FindingCurationWhereInput!] + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -2204,8 +1367,9 @@ input LogEventFieldWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -2214,748 +1378,255 @@ input LogEventFieldWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """log_event_id field predicates""" - logEventID: ID - logEventIDNEQ: ID - logEventIDIn: [ID!] - logEventIDNotIn: [ID!] - - """field_path field predicates""" - fieldPath: [String!] - fieldPathNEQ: [String!] - fieldPathIn: [[String!]!] - fieldPathNotIn: [[String!]!] - fieldPathGT: [String!] - fieldPathGTE: [String!] - fieldPathLT: [String!] - fieldPathLTE: [String!] - - """baseline_avg_bytes field predicates""" - baselineAvgBytes: Float - baselineAvgBytesNEQ: Float - baselineAvgBytesIn: [Float!] - baselineAvgBytesNotIn: [Float!] - baselineAvgBytesGT: Float - baselineAvgBytesGTE: Float - baselineAvgBytesLT: Float - baselineAvgBytesLTE: Float - baselineAvgBytesIsNil: Boolean - baselineAvgBytesNotNil: Boolean - - """last_seen_at field predicates""" - lastSeenAt: Time - lastSeenAtNEQ: Time - lastSeenAtIn: [Time!] - lastSeenAtNotIn: [Time!] - lastSeenAtGT: Time - lastSeenAtGTE: Time - lastSeenAtLT: Time - lastSeenAtLTE: Time - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """log_event edge predicates""" - hasLogEvent: Boolean - hasLogEventWith: [LogEventWhereInput!] -} - -"""Ordering options for LogEvent connections""" -input LogEventOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order LogEvents.""" - field: LogEventOrderField! -} - -"""Properties by which LogEvent connections can be ordered.""" -enum LogEventOrderField { - NAME - SEVERITY - SIGNAL_PURPOSE - EVENT_NATURE - CREATED_AT - UPDATED_AT -} - -type LogEventPolicy implements Node { - """Unique identifier""" - id: ID! - - """ - Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. - """ - accountID: UUID! - - """The log event this policy applies to""" - logEventID: ID! - - """The workspace that owns this policy""" - workspaceID: ID! - """ - Quality issue category this policy addresses. Compliance: pii_leakage, - secrets_leakage, phi_leakage, payment_data_leakage. Waste: health_checks, - bot_traffic, debug_artifacts, malformed, broken_records, commodity_traffic, - redundant_events, dead_weight. Quality: duplicate_fields, - instrumentation_bloat, oversized_fields, wrong_level. + finding_id field predicates """ - category: LogEventPolicyCategory! - + findingID: ID + findingIDNEQ: ID + findingIDIn: [ID!] + findingIDNotIn: [ID!] """ - Whether this category requires AI judgment (true) vs mechanically verifiable - (false). Auto-set via trigger from CategoryMeta. + version field predicates """ - subjective: Boolean! - + version: Int + versionNEQ: Int + versionIn: [Int!] + versionNotIn: [Int!] + versionGT: Int + versionGTE: Int + versionLT: Int + versionLTE: Int """ - Type of problem: compliance (legal/security risk), waste (event-level cuts), - or quality (field-level improvements). Auto-set via trigger from CategoryMeta. + finding_problem_version field predicates """ - categoryType: LogEventPolicyCategoryType! - + findingProblemVersion: Int + findingProblemVersionNEQ: Int + findingProblemVersionIn: [Int!] + findingProblemVersionNotIn: [Int!] + findingProblemVersionGT: Int + findingProblemVersionGTE: Int + findingProblemVersionLT: Int + findingProblemVersionLTE: Int """ - Max compliance severity across sensitivity types. NULL for non-compliance categories. Auto-set via trigger. + disposition field predicates """ - severity: LogEventPolicySeverity - + disposition: FindingCurationDisposition + dispositionNEQ: FindingCurationDisposition + dispositionIn: [FindingCurationDisposition!] + dispositionNotIn: [FindingCurationDisposition!] """ - What this policy does when enforced: 'drop' (remove all events), 'sample' - (keep at reduced rate), 'filter' (drop subset by field value), 'trim' - (remove/truncate fields), 'none' (informational only). Auto-set via trigger. + priority field predicates """ - action: LogEventPolicyAction! - - """When this policy was approved by a user""" - approvedAt: Time - - """User ID who approved this policy""" - approvedBy: String - + priority: FindingCurationPriority + priorityNEQ: FindingCurationPriority + priorityIn: [FindingCurationPriority!] + priorityNotIn: [FindingCurationPriority!] + priorityIsNil: Boolean + priorityNotNil: Boolean """ - Baseline volume/hour frozen at approval time. Snapshot of log_event.baseline_volume_per_hour. + title field predicates """ - approvedBaselineVolumePerHour: Float - + title: String + titleNEQ: String + titleIn: [String!] + titleNotIn: [String!] + titleGT: String + titleGTE: String + titleLT: String + titleLTE: String + titleContains: String + titleHasPrefix: String + titleHasSuffix: String + titleEqualFold: String + titleContainsFold: String """ - Baseline avg bytes frozen at approval time. Snapshot of log_event.baseline_avg_bytes. + body field predicates """ - approvedBaselineAvgBytes: Float - - """When this policy was dismissed by a user""" - dismissedAt: Time - - """User ID who dismissed this policy""" - dismissedBy: String - - """When this policy was created""" - createdAt: Time! - - """When this policy was last updated""" - updatedAt: Time! - - """The log event this policy applies to""" - logEvent: LogEvent! - - """The workspace that owns this policy""" - workspace: Workspace! -} - -"""LogEventPolicyAction is enum for the field action""" -enum LogEventPolicyAction { - drop - sample - filter - trim - none -} - -"""LogEventPolicyCategory is enum for the field category""" -enum LogEventPolicyCategory { - pii_leakage - secrets_leakage - phi_leakage - payment_data_leakage - health_checks - bot_traffic - debug_artifacts - malformed - broken_records - commodity_traffic - redundant_events - dead_weight - duplicate_fields - instrumentation_bloat - oversized_fields - wrong_level -} - -type LogEventPolicyCategoryStatusCache implements Node { - id: ID! - - """Account ID for tenant isolation""" - accountID: UUID! - - """Quality issue category (e.g., pii_leakage, noise, health_checks)""" - category: String! - + body: String + bodyNEQ: String + bodyIn: [String!] + bodyNotIn: [String!] + bodyGT: String + bodyGTE: String + bodyLT: String + bodyLTE: String + bodyContains: String + bodyHasPrefix: String + bodyHasSuffix: String + bodyEqualFold: String + bodyContainsFold: String """ - Type of problem: compliance (legal/security risk), waste (event-level cuts), quality (field-level improvements). + created_at field predicates """ - categoryType: LogEventPolicyCategoryStatusCacheCategoryType! - - """Human-readable category name (e.g., 'PII Leakage')""" - displayName: String! - + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time """ - Whether this category requires AI judgment (true) vs mechanically verifiable (false) + updated_at field predicates """ - subjective: Boolean! - - """What this category detects — the fundamental test for membership""" - principle: String! - - """Where this category stops applying — what NOT to flag""" - boundary: String! - + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time """ - What the policy does: drop (remove events), sample (reduce rate), filter (drop - subset), trim (modify fields), none (informational) + finding edge predicates """ - action: String - - """Policies awaiting user review in this category""" - pendingCount: Int! - - """Policies approved by user in this category""" - approvedCount: Int! - - """Policies dismissed by user in this category""" - dismissedCount: Int! - - """Pending policies with low compliance severity""" - policyPendingLowCount: Int! - - """Pending policies with medium compliance severity""" - policyPendingMediumCount: Int! - - """Pending policies with high compliance severity""" - policyPendingHighCount: Int! - - """Pending policies with critical compliance severity""" - policyPendingCriticalCount: Int! - - """Total log events that have a policy in this category""" - totalEventCount: Int! - + hasFinding: Boolean + hasFindingWith: [FindingWhereInput!] +} +""" +An edge in a connection. +""" +type FindingEdge { """ - Log events in this category that have volume data (subset of total_event_count) + The item at the end of the edge. """ - eventsWithVolumes: Int! - - """Events/hour saved by all pending policies in this category combined""" - estimatedVolumeReductionPerHour: Float - - """Bytes/hour saved by all pending policies in this category combined""" - estimatedBytesReductionPerHour: Float - + node: Finding """ - Estimated ingestion savings in USD/hour from pending policies in this category + A cursor for use in pagination. """ - estimatedCostReductionPerHourBytesUsd: Float - + cursor: Cursor! +} +""" +Ordering options for Finding connections +""" +input FindingOrder { """ - Estimated indexing savings in USD/hour from pending policies in this category + The ordering direction. """ - estimatedCostReductionPerHourVolumeUsd: Float - + direction: OrderDirection! = ASC """ - Estimated total savings in USD/hour from pending policies in this category + The field by which to order Findings. """ - estimatedCostReductionPerHourUsd: Float - refreshedAt: Time! + field: FindingOrderField! } - -""" -LogEventPolicyCategoryStatusCacheCategoryType is enum for the field category_type -""" -enum LogEventPolicyCategoryStatusCacheCategoryType { - compliance - waste - quality -} - """ -LogEventPolicyCategoryStatusCacheWhereInput is used for filtering LogEventPolicyCategoryStatusCache objects. -Input was generated by ent. +Properties by which Finding connections can be ordered. """ -input LogEventPolicyCategoryStatusCacheWhereInput { - not: LogEventPolicyCategoryStatusCacheWhereInput - and: [LogEventPolicyCategoryStatusCacheWhereInput!] - or: [LogEventPolicyCategoryStatusCacheWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """category field predicates""" - category: String - categoryNEQ: String - categoryIn: [String!] - categoryNotIn: [String!] - categoryGT: String - categoryGTE: String - categoryLT: String - categoryLTE: String - categoryContains: String - categoryHasPrefix: String - categoryHasSuffix: String - categoryEqualFold: String - categoryContainsFold: String - - """category_type field predicates""" - categoryType: LogEventPolicyCategoryStatusCacheCategoryType - categoryTypeNEQ: LogEventPolicyCategoryStatusCacheCategoryType - categoryTypeIn: [LogEventPolicyCategoryStatusCacheCategoryType!] - categoryTypeNotIn: [LogEventPolicyCategoryStatusCacheCategoryType!] - - """display_name field predicates""" - displayName: String - displayNameNEQ: String - displayNameIn: [String!] - displayNameNotIn: [String!] - displayNameGT: String - displayNameGTE: String - displayNameLT: String - displayNameLTE: String - displayNameContains: String - displayNameHasPrefix: String - displayNameHasSuffix: String - displayNameEqualFold: String - displayNameContainsFold: String - - """subjective field predicates""" - subjective: Boolean - subjectiveNEQ: Boolean - - """principle field predicates""" - principle: String - principleNEQ: String - principleIn: [String!] - principleNotIn: [String!] - principleGT: String - principleGTE: String - principleLT: String - principleLTE: String - principleContains: String - principleHasPrefix: String - principleHasSuffix: String - principleEqualFold: String - principleContainsFold: String - - """boundary field predicates""" - boundary: String - boundaryNEQ: String - boundaryIn: [String!] - boundaryNotIn: [String!] - boundaryGT: String - boundaryGTE: String - boundaryLT: String - boundaryLTE: String - boundaryContains: String - boundaryHasPrefix: String - boundaryHasSuffix: String - boundaryEqualFold: String - boundaryContainsFold: String - - """action field predicates""" - action: String - actionNEQ: String - actionIn: [String!] - actionNotIn: [String!] - actionGT: String - actionGTE: String - actionLT: String - actionLTE: String - actionContains: String - actionHasPrefix: String - actionHasSuffix: String - actionIsNil: Boolean - actionNotNil: Boolean - actionEqualFold: String - actionContainsFold: String - - """pending_count field predicates""" - pendingCount: Int - pendingCountNEQ: Int - pendingCountIn: [Int!] - pendingCountNotIn: [Int!] - pendingCountGT: Int - pendingCountGTE: Int - pendingCountLT: Int - pendingCountLTE: Int - - """approved_count field predicates""" - approvedCount: Int - approvedCountNEQ: Int - approvedCountIn: [Int!] - approvedCountNotIn: [Int!] - approvedCountGT: Int - approvedCountGTE: Int - approvedCountLT: Int - approvedCountLTE: Int - - """dismissed_count field predicates""" - dismissedCount: Int - dismissedCountNEQ: Int - dismissedCountIn: [Int!] - dismissedCountNotIn: [Int!] - dismissedCountGT: Int - dismissedCountGTE: Int - dismissedCountLT: Int - dismissedCountLTE: Int - - """policy_pending_low_count field predicates""" - policyPendingLowCount: Int - policyPendingLowCountNEQ: Int - policyPendingLowCountIn: [Int!] - policyPendingLowCountNotIn: [Int!] - policyPendingLowCountGT: Int - policyPendingLowCountGTE: Int - policyPendingLowCountLT: Int - policyPendingLowCountLTE: Int - - """policy_pending_medium_count field predicates""" - policyPendingMediumCount: Int - policyPendingMediumCountNEQ: Int - policyPendingMediumCountIn: [Int!] - policyPendingMediumCountNotIn: [Int!] - policyPendingMediumCountGT: Int - policyPendingMediumCountGTE: Int - policyPendingMediumCountLT: Int - policyPendingMediumCountLTE: Int - - """policy_pending_high_count field predicates""" - policyPendingHighCount: Int - policyPendingHighCountNEQ: Int - policyPendingHighCountIn: [Int!] - policyPendingHighCountNotIn: [Int!] - policyPendingHighCountGT: Int - policyPendingHighCountGTE: Int - policyPendingHighCountLT: Int - policyPendingHighCountLTE: Int - - """policy_pending_critical_count field predicates""" - policyPendingCriticalCount: Int - policyPendingCriticalCountNEQ: Int - policyPendingCriticalCountIn: [Int!] - policyPendingCriticalCountNotIn: [Int!] - policyPendingCriticalCountGT: Int - policyPendingCriticalCountGTE: Int - policyPendingCriticalCountLT: Int - policyPendingCriticalCountLTE: Int - - """total_event_count field predicates""" - totalEventCount: Int - totalEventCountNEQ: Int - totalEventCountIn: [Int!] - totalEventCountNotIn: [Int!] - totalEventCountGT: Int - totalEventCountGTE: Int - totalEventCountLT: Int - totalEventCountLTE: Int - - """events_with_volumes field predicates""" - eventsWithVolumes: Int - eventsWithVolumesNEQ: Int - eventsWithVolumesIn: [Int!] - eventsWithVolumesNotIn: [Int!] - eventsWithVolumesGT: Int - eventsWithVolumesGTE: Int - eventsWithVolumesLT: Int - eventsWithVolumesLTE: Int - - """estimated_volume_reduction_per_hour field predicates""" - estimatedVolumeReductionPerHour: Float - estimatedVolumeReductionPerHourNEQ: Float - estimatedVolumeReductionPerHourIn: [Float!] - estimatedVolumeReductionPerHourNotIn: [Float!] - estimatedVolumeReductionPerHourGT: Float - estimatedVolumeReductionPerHourGTE: Float - estimatedVolumeReductionPerHourLT: Float - estimatedVolumeReductionPerHourLTE: Float - estimatedVolumeReductionPerHourIsNil: Boolean - estimatedVolumeReductionPerHourNotNil: Boolean - - """estimated_bytes_reduction_per_hour field predicates""" - estimatedBytesReductionPerHour: Float - estimatedBytesReductionPerHourNEQ: Float - estimatedBytesReductionPerHourIn: [Float!] - estimatedBytesReductionPerHourNotIn: [Float!] - estimatedBytesReductionPerHourGT: Float - estimatedBytesReductionPerHourGTE: Float - estimatedBytesReductionPerHourLT: Float - estimatedBytesReductionPerHourLTE: Float - estimatedBytesReductionPerHourIsNil: Boolean - estimatedBytesReductionPerHourNotNil: Boolean - - """estimated_cost_reduction_per_hour_bytes_usd field predicates""" - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourBytesUsdNEQ: Float - estimatedCostReductionPerHourBytesUsdIn: [Float!] - estimatedCostReductionPerHourBytesUsdNotIn: [Float!] - estimatedCostReductionPerHourBytesUsdGT: Float - estimatedCostReductionPerHourBytesUsdGTE: Float - estimatedCostReductionPerHourBytesUsdLT: Float - estimatedCostReductionPerHourBytesUsdLTE: Float - estimatedCostReductionPerHourBytesUsdIsNil: Boolean - estimatedCostReductionPerHourBytesUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_volume_usd field predicates""" - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourVolumeUsdNEQ: Float - estimatedCostReductionPerHourVolumeUsdIn: [Float!] - estimatedCostReductionPerHourVolumeUsdNotIn: [Float!] - estimatedCostReductionPerHourVolumeUsdGT: Float - estimatedCostReductionPerHourVolumeUsdGTE: Float - estimatedCostReductionPerHourVolumeUsdLT: Float - estimatedCostReductionPerHourVolumeUsdLTE: Float - estimatedCostReductionPerHourVolumeUsdIsNil: Boolean - estimatedCostReductionPerHourVolumeUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_usd field predicates""" - estimatedCostReductionPerHourUsd: Float - estimatedCostReductionPerHourUsdNEQ: Float - estimatedCostReductionPerHourUsdIn: [Float!] - estimatedCostReductionPerHourUsdNotIn: [Float!] - estimatedCostReductionPerHourUsdGT: Float - estimatedCostReductionPerHourUsdGTE: Float - estimatedCostReductionPerHourUsdLT: Float - estimatedCostReductionPerHourUsdLTE: Float - estimatedCostReductionPerHourUsdIsNil: Boolean - estimatedCostReductionPerHourUsdNotNil: Boolean - - """refreshed_at field predicates""" - refreshedAt: Time - refreshedAtNEQ: Time - refreshedAtIn: [Time!] - refreshedAtNotIn: [Time!] - refreshedAtGT: Time - refreshedAtGTE: Time - refreshedAtLT: Time - refreshedAtLTE: Time -} - -"""LogEventPolicyCategoryType is enum for the field category_type""" -enum LogEventPolicyCategoryType { - compliance - waste - quality -} - -"""A connection to a list of items.""" -type LogEventPolicyConnection { - """A list of edges.""" - edges: [LogEventPolicyEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" - totalCount: Int! -} - -"""An edge in a connection.""" -type LogEventPolicyEdge { - """The item at the end of the edge.""" - node: LogEventPolicy - - """A cursor for use in pagination.""" - cursor: Cursor! -} - -"""Ordering options for LogEventPolicy connections""" -input LogEventPolicyOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order LogEventPolicies.""" - field: LogEventPolicyOrderField! -} - -"""Properties by which LogEventPolicy connections can be ordered.""" -enum LogEventPolicyOrderField { - CATEGORY - SUBJECTIVE - CATEGORY_TYPE - SEVERITY - ACTION - APPROVED_AT - DISMISSED_AT +enum FindingOrderField { + DOMAIN + TYPE + PROBLEM_VERSION + FINGERPRINT + CLOSED_AT CREATED_AT UPDATED_AT } - -"""LogEventPolicySeverity is enum for the field severity""" -enum LogEventPolicySeverity { - low - medium - high - critical -} - -type LogEventPolicyStatusCache implements Node { +type FindingPlan implements Node { + """ + Unique identifier + """ id: ID! - - """Account ID for tenant isolation""" + """ + Denormalized for tenant isolation. Auto-set via trigger from finding.account_id. + """ accountID: UUID! - - """The policy this status row represents""" - policyID: ID! - - """The log event this policy targets""" - logEventID: UUID! - - """The workspace that owns this policy""" - workspaceID: UUID! - """ - Quality issue category this policy addresses (e.g., pii_leakage, noise, health_checks) + The finding this plan belongs to """ - category: String! - + findingID: ID! """ - User decision on this policy. PENDING (awaiting review), APPROVED (accepted - for enforcement), DISMISSED (rejected by user). + The curation that produced this plan revision """ - status: LogEventPolicyStatusCacheStatus! - + findingCurationID: ID! """ - Whether this category requires AI judgment (true) vs mechanically verifiable (false) + Monotonic revision number for this finding's plan. """ - subjective: Boolean! - + revision: Int! """ - Type of problem: compliance (legal/security risk), waste (event-level cuts), quality (field-level improvements). + Version of the plan contract and prompt that produced this record. """ - categoryType: LogEventPolicyStatusCacheCategoryType! - + version: Int! """ - Max compliance severity across sensitivity types. NULL for non-compliance categories. + Current lifecycle state for this plan revision. Values: draft = Initial working draft.; needs_input = Waiting on missing context or operator input.; ready_for_review = Ready for human review.; approved = Approved for execution.; executing = Execution is in progress.; completed = Execution finished successfully.; failed = Execution or validation failed.; superseded = Replaced by a newer revision.; canceled = Canceled before completion. + """ + status: FindingPlanStatus! + """ + Short user-facing title for this plan. + """ + title: String! + """ + Summary of the remediation plan. + """ + summary: String! + """ + Why this plan is the preferred remediation path. + """ + rationale: String! + """ + When this plan row was created. """ - severity: LogEventPolicyStatusCacheSeverity - - """When this policy was approved by a user""" - approvedAt: Time - - """When this policy was dismissed by a user""" - dismissedAt: Time - - """When this policy was created""" createdAt: Time! - """ - What the policy does: drop (remove events), sample (reduce rate), filter (drop - subset), trim (modify fields), none (informational) + When this plan row was last updated. """ - action: String - + updatedAt: Time! """ - Fraction of events that survive this policy (0.0 = all dropped, 1.0 = all kept). NULL if not estimable. + The finding this plan belongs to """ - survivalRate: Float - - """Events/hour saved if this policy applied alone. NULL if not estimable.""" - estimatedVolumeReductionPerHour: Float - - """Bytes/hour saved if this policy applied alone. NULL if not estimable.""" - estimatedBytesReductionPerHour: Float - - """Estimated ingestion savings in USD/hour from bytes reduction""" - estimatedCostReductionPerHourBytesUsd: Float - - """Estimated indexing savings in USD/hour from volume reduction""" - estimatedCostReductionPerHourVolumeUsd: Float - - """Estimated total savings in USD/hour (bytes + volume)""" - estimatedCostReductionPerHourUsd: Float - - """Service that produces the targeted log event (denormalized)""" - serviceID: UUID - - """Name of the service (denormalized for display)""" - serviceName: String - - """Name of the targeted log event (denormalized for display)""" - logEventName: String - - """Current throughput of the targeted log event in events/hour""" - volumePerHour: Float - - """Current throughput of the targeted log event in bytes/hour""" - bytesPerHour: Float - refreshedAt: Time! + finding: Finding! + """ + The curation that produced this plan revision + """ + findingCuration: FindingCuration! } - """ -LogEventPolicyStatusCacheCategoryType is enum for the field category_type +Ordering options for FindingPlan connections """ -enum LogEventPolicyStatusCacheCategoryType { - compliance - waste - quality +input FindingPlanOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order FindingPlans. + """ + field: FindingPlanOrderField! } - -"""LogEventPolicyStatusCacheSeverity is enum for the field severity""" -enum LogEventPolicyStatusCacheSeverity { - low - medium - high - critical +""" +Properties by which FindingPlan connections can be ordered. +""" +enum FindingPlanOrderField { + REVISION + VERSION + STATUS + CREATED_AT + UPDATED_AT } - -"""LogEventPolicyStatusCacheStatus is enum for the field status""" -enum LogEventPolicyStatusCacheStatus { - PENDING - APPROVED - DISMISSED +""" +FindingPlanStatus is enum for the field status +""" +enum FindingPlanStatus @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/findingplan.Status") { + draft + needs_input + ready_for_review + approved + executing + completed + failed + superseded + canceled } - """ -LogEventPolicyStatusCacheWhereInput is used for filtering LogEventPolicyStatusCache objects. +FindingPlanWhereInput is used for filtering FindingPlan objects. Input was generated by ent. """ -input LogEventPolicyStatusCacheWhereInput { - not: LogEventPolicyStatusCacheWhereInput - and: [LogEventPolicyStatusCacheWhereInput!] - or: [LogEventPolicyStatusCacheWhereInput!] - - """id field predicates""" +input FindingPlanWhereInput { + not: FindingPlanWhereInput + and: [FindingPlanWhereInput!] + or: [FindingPlanWhereInput!] + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -2964,8 +1635,9 @@ input LogEventPolicyStatusCacheWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -2974,276 +1646,665 @@ input LogEventPolicyStatusCacheWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """policy_id field predicates""" - policyID: ID - policyIDNEQ: ID - policyIDIn: [ID!] - policyIDNotIn: [ID!] - - """log_event_id field predicates""" - logEventID: UUID - logEventIDNEQ: UUID - logEventIDIn: [UUID!] - logEventIDNotIn: [UUID!] - logEventIDGT: UUID - logEventIDGTE: UUID - logEventIDLT: UUID - logEventIDLTE: UUID - - """workspace_id field predicates""" - workspaceID: UUID - workspaceIDNEQ: UUID - workspaceIDIn: [UUID!] - workspaceIDNotIn: [UUID!] - workspaceIDGT: UUID - workspaceIDGTE: UUID - workspaceIDLT: UUID - workspaceIDLTE: UUID - - """category field predicates""" - category: String - categoryNEQ: String - categoryIn: [String!] - categoryNotIn: [String!] - categoryGT: String - categoryGTE: String - categoryLT: String - categoryLTE: String - categoryContains: String - categoryHasPrefix: String - categoryHasSuffix: String - categoryEqualFold: String - categoryContainsFold: String - - """status field predicates""" - status: LogEventPolicyStatusCacheStatus - statusNEQ: LogEventPolicyStatusCacheStatus - statusIn: [LogEventPolicyStatusCacheStatus!] - statusNotIn: [LogEventPolicyStatusCacheStatus!] - - """subjective field predicates""" - subjective: Boolean - subjectiveNEQ: Boolean - - """category_type field predicates""" - categoryType: LogEventPolicyStatusCacheCategoryType - categoryTypeNEQ: LogEventPolicyStatusCacheCategoryType - categoryTypeIn: [LogEventPolicyStatusCacheCategoryType!] - categoryTypeNotIn: [LogEventPolicyStatusCacheCategoryType!] - - """severity field predicates""" - severity: LogEventPolicyStatusCacheSeverity - severityNEQ: LogEventPolicyStatusCacheSeverity - severityIn: [LogEventPolicyStatusCacheSeverity!] - severityNotIn: [LogEventPolicyStatusCacheSeverity!] - severityIsNil: Boolean - severityNotNil: Boolean - - """approved_at field predicates""" - approvedAt: Time - approvedAtNEQ: Time - approvedAtIn: [Time!] - approvedAtNotIn: [Time!] - approvedAtGT: Time - approvedAtGTE: Time - approvedAtLT: Time - approvedAtLTE: Time - approvedAtIsNil: Boolean - approvedAtNotNil: Boolean - - """dismissed_at field predicates""" - dismissedAt: Time - dismissedAtNEQ: Time - dismissedAtIn: [Time!] - dismissedAtNotIn: [Time!] - dismissedAtGT: Time - dismissedAtGTE: Time - dismissedAtLT: Time - dismissedAtLTE: Time - dismissedAtIsNil: Boolean - dismissedAtNotNil: Boolean - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time + """ + finding_id field predicates + """ + findingID: ID + findingIDNEQ: ID + findingIDIn: [ID!] + findingIDNotIn: [ID!] + """ + finding_curation_id field predicates + """ + findingCurationID: ID + findingCurationIDNEQ: ID + findingCurationIDIn: [ID!] + findingCurationIDNotIn: [ID!] + """ + revision field predicates + """ + revision: Int + revisionNEQ: Int + revisionIn: [Int!] + revisionNotIn: [Int!] + revisionGT: Int + revisionGTE: Int + revisionLT: Int + revisionLTE: Int + """ + version field predicates + """ + version: Int + versionNEQ: Int + versionIn: [Int!] + versionNotIn: [Int!] + versionGT: Int + versionGTE: Int + versionLT: Int + versionLTE: Int + """ + status field predicates + """ + status: FindingPlanStatus + statusNEQ: FindingPlanStatus + statusIn: [FindingPlanStatus!] + statusNotIn: [FindingPlanStatus!] + """ + title field predicates + """ + title: String + titleNEQ: String + titleIn: [String!] + titleNotIn: [String!] + titleGT: String + titleGTE: String + titleLT: String + titleLTE: String + titleContains: String + titleHasPrefix: String + titleHasSuffix: String + titleEqualFold: String + titleContainsFold: String + """ + summary field predicates + """ + summary: String + summaryNEQ: String + summaryIn: [String!] + summaryNotIn: [String!] + summaryGT: String + summaryGTE: String + summaryLT: String + summaryLTE: String + summaryContains: String + summaryHasPrefix: String + summaryHasSuffix: String + summaryEqualFold: String + summaryContainsFold: String + """ + rationale field predicates + """ + rationale: String + rationaleNEQ: String + rationaleIn: [String!] + rationaleNotIn: [String!] + rationaleGT: String + rationaleGTE: String + rationaleLT: String + rationaleLTE: String + rationaleContains: String + rationaleHasPrefix: String + rationaleHasSuffix: String + rationaleEqualFold: String + rationaleContainsFold: String + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """action field predicates""" - action: String - actionNEQ: String - actionIn: [String!] - actionNotIn: [String!] - actionGT: String - actionGTE: String - actionLT: String - actionLTE: String - actionContains: String - actionHasPrefix: String - actionHasSuffix: String - actionIsNil: Boolean - actionNotNil: Boolean - actionEqualFold: String - actionContainsFold: String - - """survival_rate field predicates""" - survivalRate: Float - survivalRateNEQ: Float - survivalRateIn: [Float!] - survivalRateNotIn: [Float!] - survivalRateGT: Float - survivalRateGTE: Float - survivalRateLT: Float - survivalRateLTE: Float - survivalRateIsNil: Boolean - survivalRateNotNil: Boolean - - """estimated_volume_reduction_per_hour field predicates""" - estimatedVolumeReductionPerHour: Float - estimatedVolumeReductionPerHourNEQ: Float - estimatedVolumeReductionPerHourIn: [Float!] - estimatedVolumeReductionPerHourNotIn: [Float!] - estimatedVolumeReductionPerHourGT: Float - estimatedVolumeReductionPerHourGTE: Float - estimatedVolumeReductionPerHourLT: Float - estimatedVolumeReductionPerHourLTE: Float - estimatedVolumeReductionPerHourIsNil: Boolean - estimatedVolumeReductionPerHourNotNil: Boolean - - """estimated_bytes_reduction_per_hour field predicates""" - estimatedBytesReductionPerHour: Float - estimatedBytesReductionPerHourNEQ: Float - estimatedBytesReductionPerHourIn: [Float!] - estimatedBytesReductionPerHourNotIn: [Float!] - estimatedBytesReductionPerHourGT: Float - estimatedBytesReductionPerHourGTE: Float - estimatedBytesReductionPerHourLT: Float - estimatedBytesReductionPerHourLTE: Float - estimatedBytesReductionPerHourIsNil: Boolean - estimatedBytesReductionPerHourNotNil: Boolean - - """estimated_cost_reduction_per_hour_bytes_usd field predicates""" - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourBytesUsdNEQ: Float - estimatedCostReductionPerHourBytesUsdIn: [Float!] - estimatedCostReductionPerHourBytesUsdNotIn: [Float!] - estimatedCostReductionPerHourBytesUsdGT: Float - estimatedCostReductionPerHourBytesUsdGTE: Float - estimatedCostReductionPerHourBytesUsdLT: Float - estimatedCostReductionPerHourBytesUsdLTE: Float - estimatedCostReductionPerHourBytesUsdIsNil: Boolean - estimatedCostReductionPerHourBytesUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_volume_usd field predicates""" - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourVolumeUsdNEQ: Float - estimatedCostReductionPerHourVolumeUsdIn: [Float!] - estimatedCostReductionPerHourVolumeUsdNotIn: [Float!] - estimatedCostReductionPerHourVolumeUsdGT: Float - estimatedCostReductionPerHourVolumeUsdGTE: Float - estimatedCostReductionPerHourVolumeUsdLT: Float - estimatedCostReductionPerHourVolumeUsdLTE: Float - estimatedCostReductionPerHourVolumeUsdIsNil: Boolean - estimatedCostReductionPerHourVolumeUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_usd field predicates""" - estimatedCostReductionPerHourUsd: Float - estimatedCostReductionPerHourUsdNEQ: Float - estimatedCostReductionPerHourUsdIn: [Float!] - estimatedCostReductionPerHourUsdNotIn: [Float!] - estimatedCostReductionPerHourUsdGT: Float - estimatedCostReductionPerHourUsdGTE: Float - estimatedCostReductionPerHourUsdLT: Float - estimatedCostReductionPerHourUsdLTE: Float - estimatedCostReductionPerHourUsdIsNil: Boolean - estimatedCostReductionPerHourUsdNotNil: Boolean - - """service_id field predicates""" - serviceID: UUID - serviceIDNEQ: UUID - serviceIDIn: [UUID!] - serviceIDNotIn: [UUID!] - serviceIDGT: UUID - serviceIDGTE: UUID - serviceIDLT: UUID - serviceIDLTE: UUID + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + finding edge predicates + """ + hasFinding: Boolean + hasFindingWith: [FindingWhereInput!] + """ + finding_curation edge predicates + """ + hasFindingCuration: Boolean + hasFindingCurationWith: [FindingCurationWhereInput!] +} +""" +FindingScopeKind is enum for the field scope_kind +""" +enum FindingScopeKind @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/finding.ScopeKind") { + account + service + log_event +} +""" +FindingWhereInput is used for filtering Finding objects. +Input was generated by ent. +""" +input FindingWhereInput { + not: FindingWhereInput + and: [FindingWhereInput!] + or: [FindingWhereInput!] + """ + id field predicates + """ + id: ID + idNEQ: ID + idIn: [ID!] + idNotIn: [ID!] + idGT: ID + idGTE: ID + idLT: ID + idLTE: ID + """ + account_id field predicates + """ + accountID: ID + accountIDNEQ: ID + accountIDIn: [ID!] + accountIDNotIn: [ID!] + """ + service_id field predicates + """ + serviceID: ID + serviceIDNEQ: ID + serviceIDIn: [ID!] + serviceIDNotIn: [ID!] serviceIDIsNil: Boolean serviceIDNotNil: Boolean - - """service_name field predicates""" - serviceName: String - serviceNameNEQ: String - serviceNameIn: [String!] - serviceNameNotIn: [String!] - serviceNameGT: String - serviceNameGTE: String - serviceNameLT: String - serviceNameLTE: String - serviceNameContains: String - serviceNameHasPrefix: String - serviceNameHasSuffix: String - serviceNameIsNil: Boolean - serviceNameNotNil: Boolean - serviceNameEqualFold: String - serviceNameContainsFold: String - - """log_event_name field predicates""" - logEventName: String - logEventNameNEQ: String - logEventNameIn: [String!] - logEventNameNotIn: [String!] - logEventNameGT: String - logEventNameGTE: String - logEventNameLT: String - logEventNameLTE: String - logEventNameContains: String - logEventNameHasPrefix: String - logEventNameHasSuffix: String - logEventNameIsNil: Boolean - logEventNameNotNil: Boolean - logEventNameEqualFold: String - logEventNameContainsFold: String - - """volume_per_hour field predicates""" - volumePerHour: Float - volumePerHourNEQ: Float - volumePerHourIn: [Float!] - volumePerHourNotIn: [Float!] - volumePerHourGT: Float - volumePerHourGTE: Float - volumePerHourLT: Float - volumePerHourLTE: Float - volumePerHourIsNil: Boolean - volumePerHourNotNil: Boolean - - """bytes_per_hour field predicates""" - bytesPerHour: Float - bytesPerHourNEQ: Float - bytesPerHourIn: [Float!] - bytesPerHourNotIn: [Float!] - bytesPerHourGT: Float - bytesPerHourGTE: Float - bytesPerHourLT: Float - bytesPerHourLTE: Float - bytesPerHourIsNil: Boolean - bytesPerHourNotNil: Boolean - - """refreshed_at field predicates""" - refreshedAt: Time - refreshedAtNEQ: Time - refreshedAtIn: [Time!] - refreshedAtNotIn: [Time!] - refreshedAtGT: Time - refreshedAtGTE: Time - refreshedAtLT: Time - refreshedAtLTE: Time + """ + log_event_id field predicates + """ + logEventID: ID + logEventIDNEQ: ID + logEventIDIn: [ID!] + logEventIDNotIn: [ID!] + logEventIDIsNil: Boolean + logEventIDNotNil: Boolean + """ + scope_kind field predicates + """ + scopeKind: FindingScopeKind + scopeKindNEQ: FindingScopeKind + scopeKindIn: [FindingScopeKind!] + scopeKindNotIn: [FindingScopeKind!] + """ + domain field predicates + """ + domain: String + domainNEQ: String + domainIn: [String!] + domainNotIn: [String!] + domainGT: String + domainGTE: String + domainLT: String + domainLTE: String + domainContains: String + domainHasPrefix: String + domainHasSuffix: String + domainEqualFold: String + domainContainsFold: String + """ + type field predicates + """ + type: String + typeNEQ: String + typeIn: [String!] + typeNotIn: [String!] + typeGT: String + typeGTE: String + typeLT: String + typeLTE: String + typeContains: String + typeHasPrefix: String + typeHasSuffix: String + typeEqualFold: String + typeContainsFold: String + """ + problem_version field predicates + """ + problemVersion: Int + problemVersionNEQ: Int + problemVersionIn: [Int!] + problemVersionNotIn: [Int!] + problemVersionGT: Int + problemVersionGTE: Int + problemVersionLT: Int + problemVersionLTE: Int + """ + fingerprint field predicates + """ + fingerprint: String + fingerprintNEQ: String + fingerprintIn: [String!] + fingerprintNotIn: [String!] + fingerprintGT: String + fingerprintGTE: String + fingerprintLT: String + fingerprintLTE: String + fingerprintContains: String + fingerprintHasPrefix: String + fingerprintHasSuffix: String + fingerprintEqualFold: String + fingerprintContainsFold: String + """ + closed_at field predicates + """ + closedAt: Time + closedAtNEQ: Time + closedAtIn: [Time!] + closedAtNotIn: [Time!] + closedAtGT: Time + closedAtGTE: Time + closedAtLT: Time + closedAtLTE: Time + closedAtIsNil: Boolean + closedAtNotNil: Boolean + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + account edge predicates + """ + hasAccount: Boolean + hasAccountWith: [AccountWhereInput!] + """ + service edge predicates + """ + hasService: Boolean + hasServiceWith: [ServiceWhereInput!] + """ + log_event edge predicates + """ + hasLogEvent: Boolean + hasLogEventWith: [LogEventWhereInput!] + """ + finding_curation edge predicates + """ + hasFindingCuration: Boolean + hasFindingCurationWith: [FindingCurationWhereInput!] + """ + finding_plans edge predicates + """ + hasFindingPlans: Boolean + hasFindingPlansWith: [FindingPlanWhereInput!] + """ + log_event_policies edge predicates + """ + hasLogEventPolicies: Boolean + hasLogEventPoliciesWith: [LogEventPolicyWhereInput!] +} +type LogEvent implements Node { + """ + Unique identifier of the log event + """ + id: ID! + """ + Denormalized for tenant isolation. Auto-set via trigger from service.account_id. + """ + accountID: UUID! + """ + Service that produces this event + """ + serviceID: ID! + """ + Snake_case identifier unique per service, e.g. nginx_access_log + """ + name: String! + """ + What the event is and what data instances carry. Helps engineers decide whether to look here. + """ + description: String! + """ + Predominant log severity level, derived from example records. Nullable when examples have no severity info. Values: debug = The event usually appears at debug severity.; info = The event usually appears at info severity.; warn = The event usually appears at warn severity.; error = The event usually appears at error severity.; other = The event usually appears at a non-standard or other severity. + """ + severity: LogEventSeverity + """ + Current trailing 7-day average events/hour. Refreshed on volume ingestion. + """ + baselineVolumePerHour: Float + """ + Current trailing 7-day volume-weighted average bytes/event. Refreshed on volume ingestion. + """ + baselineAvgBytes: Float + """ + When the log event was created + """ + createdAt: Time! + """ + When the log event was last updated + """ + updatedAt: Time! + """ + Service that produces this event + """ + service: Service! + """ + Extensible typed facts attached to this log event + """ + facts: [LogEventFact!] + """ + Contribution, preview, and effective per-log-event policy rows. + """ + logEventPolicies: [LogEventPolicy!] +} +""" +A connection to a list of items. +""" +type LogEventConnection { + """ + A list of edges. + """ + edges: [LogEventEdge] + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} +""" +An edge in a connection. +""" +type LogEventEdge { + """ + The item at the end of the edge. + """ + node: LogEvent + """ + A cursor for use in pagination. + """ + cursor: Cursor! +} +type LogEventFact implements Node { + """ + Unique identifier + """ + id: ID! + """ + Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. + """ + accountID: UUID! + """ + The log event this fact belongs to + """ + logEventID: ID! + """ + Owned fact slice refreshed together, e.g. identity, meaning, quality. + """ + sliceName: String! + """ + Current code version applied for this slice. + """ + sliceVersion: Int! + """ + Fact name within the slice, e.g. identity_profile, operational_role, value_profile. + """ + factName: String! + """ + Typed fact payload stored as JSONB. Shape is determined by slice_name + fact_name. + """ + value: Map! + """ + When this fact was first recorded + """ + createdAt: Time! + """ + When this fact was last refreshed + """ + updatedAt: Time! + """ + The log event this fact belongs to + """ + logEvent: LogEvent! +} +""" +Ordering options for LogEventFact connections +""" +input LogEventFactOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order LogEventFacts. + """ + field: LogEventFactOrderField! +} +""" +Properties by which LogEventFact connections can be ordered. +""" +enum LogEventFactOrderField { + SLICE_NAME + SLICE_VERSION + FACT_NAME + CREATED_AT + UPDATED_AT +} +""" +LogEventFactWhereInput is used for filtering LogEventFact objects. +Input was generated by ent. +""" +input LogEventFactWhereInput { + not: LogEventFactWhereInput + and: [LogEventFactWhereInput!] + or: [LogEventFactWhereInput!] + """ + id field predicates + """ + id: ID + idNEQ: ID + idIn: [ID!] + idNotIn: [ID!] + idGT: ID + idGTE: ID + idLT: ID + idLTE: ID + """ + account_id field predicates + """ + accountID: UUID + accountIDNEQ: UUID + accountIDIn: [UUID!] + accountIDNotIn: [UUID!] + accountIDGT: UUID + accountIDGTE: UUID + accountIDLT: UUID + accountIDLTE: UUID + """ + log_event_id field predicates + """ + logEventID: ID + logEventIDNEQ: ID + logEventIDIn: [ID!] + logEventIDNotIn: [ID!] + """ + slice_name field predicates + """ + sliceName: String + sliceNameNEQ: String + sliceNameIn: [String!] + sliceNameNotIn: [String!] + sliceNameGT: String + sliceNameGTE: String + sliceNameLT: String + sliceNameLTE: String + sliceNameContains: String + sliceNameHasPrefix: String + sliceNameHasSuffix: String + sliceNameEqualFold: String + sliceNameContainsFold: String + """ + slice_version field predicates + """ + sliceVersion: Int + sliceVersionNEQ: Int + sliceVersionIn: [Int!] + sliceVersionNotIn: [Int!] + sliceVersionGT: Int + sliceVersionGTE: Int + sliceVersionLT: Int + sliceVersionLTE: Int + """ + fact_name field predicates + """ + factName: String + factNameNEQ: String + factNameIn: [String!] + factNameNotIn: [String!] + factNameGT: String + factNameGTE: String + factNameLT: String + factNameLTE: String + factNameContains: String + factNameHasPrefix: String + factNameHasSuffix: String + factNameEqualFold: String + factNameContainsFold: String + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + log_event edge predicates + """ + hasLogEvent: Boolean + hasLogEventWith: [LogEventWhereInput!] +} +""" +Ordering options for LogEvent connections +""" +input LogEventOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order LogEvents. + """ + field: LogEventOrderField! +} +""" +Properties by which LogEvent connections can be ordered. +""" +enum LogEventOrderField { + NAME + SEVERITY + CREATED_AT + UPDATED_AT +} +type LogEventPolicy implements Node { + """ + Unique identifier + """ + id: ID! + """ + Denormalized for tenant isolation. Auto-set via trigger from log_event.account_id. + """ + accountID: UUID! + """ + Owning finding for contribution rows. Null for merged preview/effective rows. + """ + findingID: ID + """ + The log event this policy applies to + """ + logEventID: ID! + """ + Policy role for this log event. Values: contribution = One finding's contribution row.; preview = Merged preview state before enforcement.; effective = Merged enforced state currently in effect. + """ + kind: LogEventPolicyKind! + """ + When this policy row was last compiled from its current inputs. + """ + compiledAt: Time! + """ + When this policy row was created. + """ + createdAt: Time! + """ + When this policy row was last updated. + """ + updatedAt: Time! + """ + Finding that owns this contribution row when kind=contribution + """ + finding: Finding + """ + The log event this policy applies to + """ + logEvent: LogEvent! +} +""" +LogEventPolicyKind is enum for the field kind +""" +enum LogEventPolicyKind @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/logeventpolicy.Kind") { + contribution + preview + effective +} +""" +Ordering options for LogEventPolicy connections +""" +input LogEventPolicyOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order LogEventPolicies. + """ + field: LogEventPolicyOrderField! +} +""" +Properties by which LogEventPolicy connections can be ordered. +""" +enum LogEventPolicyOrderField { + KIND + COMPILED_AT + CREATED_AT + UPDATED_AT } - """ LogEventPolicyWhereInput is used for filtering LogEventPolicy objects. Input was generated by ent. @@ -3252,8 +2313,9 @@ input LogEventPolicyWhereInput { not: LogEventPolicyWhereInput and: [LogEventPolicyWhereInput!] or: [LogEventPolicyWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -3262,8 +2324,9 @@ input LogEventPolicyWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -3272,147 +2335,43 @@ input LogEventPolicyWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """log_event_id field predicates""" + """ + finding_id field predicates + """ + findingID: ID + findingIDNEQ: ID + findingIDIn: [ID!] + findingIDNotIn: [ID!] + findingIDIsNil: Boolean + findingIDNotNil: Boolean + """ + log_event_id field predicates + """ logEventID: ID logEventIDNEQ: ID logEventIDIn: [ID!] logEventIDNotIn: [ID!] - - """workspace_id field predicates""" - workspaceID: ID - workspaceIDNEQ: ID - workspaceIDIn: [ID!] - workspaceIDNotIn: [ID!] - - """category field predicates""" - category: LogEventPolicyCategory - categoryNEQ: LogEventPolicyCategory - categoryIn: [LogEventPolicyCategory!] - categoryNotIn: [LogEventPolicyCategory!] - - """subjective field predicates""" - subjective: Boolean - subjectiveNEQ: Boolean - - """category_type field predicates""" - categoryType: LogEventPolicyCategoryType - categoryTypeNEQ: LogEventPolicyCategoryType - categoryTypeIn: [LogEventPolicyCategoryType!] - categoryTypeNotIn: [LogEventPolicyCategoryType!] - - """severity field predicates""" - severity: LogEventPolicySeverity - severityNEQ: LogEventPolicySeverity - severityIn: [LogEventPolicySeverity!] - severityNotIn: [LogEventPolicySeverity!] - severityIsNil: Boolean - severityNotNil: Boolean - - """action field predicates""" - action: LogEventPolicyAction - actionNEQ: LogEventPolicyAction - actionIn: [LogEventPolicyAction!] - actionNotIn: [LogEventPolicyAction!] - - """approved_at field predicates""" - approvedAt: Time - approvedAtNEQ: Time - approvedAtIn: [Time!] - approvedAtNotIn: [Time!] - approvedAtGT: Time - approvedAtGTE: Time - approvedAtLT: Time - approvedAtLTE: Time - approvedAtIsNil: Boolean - approvedAtNotNil: Boolean - - """approved_by field predicates""" - approvedBy: String - approvedByNEQ: String - approvedByIn: [String!] - approvedByNotIn: [String!] - approvedByGT: String - approvedByGTE: String - approvedByLT: String - approvedByLTE: String - approvedByContains: String - approvedByHasPrefix: String - approvedByHasSuffix: String - approvedByIsNil: Boolean - approvedByNotNil: Boolean - approvedByEqualFold: String - approvedByContainsFold: String - - """approved_baseline_volume_per_hour field predicates""" - approvedBaselineVolumePerHour: Float - approvedBaselineVolumePerHourNEQ: Float - approvedBaselineVolumePerHourIn: [Float!] - approvedBaselineVolumePerHourNotIn: [Float!] - approvedBaselineVolumePerHourGT: Float - approvedBaselineVolumePerHourGTE: Float - approvedBaselineVolumePerHourLT: Float - approvedBaselineVolumePerHourLTE: Float - approvedBaselineVolumePerHourIsNil: Boolean - approvedBaselineVolumePerHourNotNil: Boolean - - """approved_baseline_avg_bytes field predicates""" - approvedBaselineAvgBytes: Float - approvedBaselineAvgBytesNEQ: Float - approvedBaselineAvgBytesIn: [Float!] - approvedBaselineAvgBytesNotIn: [Float!] - approvedBaselineAvgBytesGT: Float - approvedBaselineAvgBytesGTE: Float - approvedBaselineAvgBytesLT: Float - approvedBaselineAvgBytesLTE: Float - approvedBaselineAvgBytesIsNil: Boolean - approvedBaselineAvgBytesNotNil: Boolean - - """dismissed_at field predicates""" - dismissedAt: Time - dismissedAtNEQ: Time - dismissedAtIn: [Time!] - dismissedAtNotIn: [Time!] - dismissedAtGT: Time - dismissedAtGTE: Time - dismissedAtLT: Time - dismissedAtLTE: Time - dismissedAtIsNil: Boolean - dismissedAtNotNil: Boolean - - """dismissed_by field predicates""" - dismissedBy: String - dismissedByNEQ: String - dismissedByIn: [String!] - dismissedByNotIn: [String!] - dismissedByGT: String - dismissedByGTE: String - dismissedByLT: String - dismissedByLTE: String - dismissedByContains: String - dismissedByHasPrefix: String - dismissedByHasSuffix: String - dismissedByIsNil: Boolean - dismissedByNotNil: Boolean - dismissedByEqualFold: String - dismissedByContainsFold: String - - """model field predicates""" - model: String - modelNEQ: String - modelIn: [String!] - modelNotIn: [String!] - modelGT: String - modelGTE: String - modelLT: String - modelLTE: String - modelContains: String - modelHasPrefix: String - modelHasSuffix: String - modelEqualFold: String - modelContainsFold: String - - """created_at field predicates""" + """ + kind field predicates + """ + kind: LogEventPolicyKind + kindNEQ: LogEventPolicyKind + kindIn: [LogEventPolicyKind!] + kindNotIn: [LogEventPolicyKind!] + """ + compiled_at field predicates + """ + compiledAt: Time + compiledAtNEQ: Time + compiledAtIn: [Time!] + compiledAtNotIn: [Time!] + compiledAtGT: Time + compiledAtGTE: Time + compiledAtLT: Time + compiledAtLTE: Time + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -3421,8 +2380,9 @@ input LogEventPolicyWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -3431,455 +2391,27 @@ input LogEventPolicyWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """log_event edge predicates""" + """ + finding edge predicates + """ + hasFinding: Boolean + hasFindingWith: [FindingWhereInput!] + """ + log_event edge predicates + """ hasLogEvent: Boolean hasLogEventWith: [LogEventWhereInput!] - - """workspace edge predicates""" - hasWorkspace: Boolean - hasWorkspaceWith: [WorkspaceWhereInput!] } - -"""LogEventSeverity is enum for the field severity""" -enum LogEventSeverity { +""" +LogEventSeverity is enum for the field severity +""" +enum LogEventSeverity @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/logevent.Severity") { debug info warn error other } - -"""LogEventSignalPurpose is enum for the field signal_purpose""" -enum LogEventSignalPurpose { - diagnostic - operational - lifecycle - ephemeral -} - -type LogEventStatusCache implements Node { - id: ID! - accountID: UUID! - logEventID: ID! - serviceID: UUID! - hasVolumes: Boolean! - hasBeenAnalyzed: Boolean! - policyCount: Int! - pendingPolicyCount: Int! - approvedPolicyCount: Int! - dismissedPolicyCount: Int! - policyPendingLowCount: Int! - policyPendingMediumCount: Int! - policyPendingHighCount: Int! - policyPendingCriticalCount: Int! - estimatedVolumeReductionPerHour: Float - estimatedBytesReductionPerHour: Float - volumePerHour: Float - bytesPerHour: Float - costPerHourBytesUsd: Float - costPerHourVolumeUsd: Float - costPerHourUsd: Float - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourUsd: Float - observedVolumePerHourBefore: Float - observedVolumePerHourAfter: Float - observedBytesPerHourBefore: Float - observedBytesPerHourAfter: Float - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeUsd: Float - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterUsd: Float - refreshedAt: Time! -} - -""" -LogEventStatusCacheWhereInput is used for filtering LogEventStatusCache objects. -Input was generated by ent. -""" -input LogEventStatusCacheWhereInput { - not: LogEventStatusCacheWhereInput - and: [LogEventStatusCacheWhereInput!] - or: [LogEventStatusCacheWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """log_event_id field predicates""" - logEventID: ID - logEventIDNEQ: ID - logEventIDIn: [ID!] - logEventIDNotIn: [ID!] - - """service_id field predicates""" - serviceID: UUID - serviceIDNEQ: UUID - serviceIDIn: [UUID!] - serviceIDNotIn: [UUID!] - serviceIDGT: UUID - serviceIDGTE: UUID - serviceIDLT: UUID - serviceIDLTE: UUID - - """has_volumes field predicates""" - hasVolumes: Boolean - hasVolumesNEQ: Boolean - - """has_been_analyzed field predicates""" - hasBeenAnalyzed: Boolean - hasBeenAnalyzedNEQ: Boolean - - """policy_count field predicates""" - policyCount: Int - policyCountNEQ: Int - policyCountIn: [Int!] - policyCountNotIn: [Int!] - policyCountGT: Int - policyCountGTE: Int - policyCountLT: Int - policyCountLTE: Int - - """pending_policy_count field predicates""" - pendingPolicyCount: Int - pendingPolicyCountNEQ: Int - pendingPolicyCountIn: [Int!] - pendingPolicyCountNotIn: [Int!] - pendingPolicyCountGT: Int - pendingPolicyCountGTE: Int - pendingPolicyCountLT: Int - pendingPolicyCountLTE: Int - - """approved_policy_count field predicates""" - approvedPolicyCount: Int - approvedPolicyCountNEQ: Int - approvedPolicyCountIn: [Int!] - approvedPolicyCountNotIn: [Int!] - approvedPolicyCountGT: Int - approvedPolicyCountGTE: Int - approvedPolicyCountLT: Int - approvedPolicyCountLTE: Int - - """dismissed_policy_count field predicates""" - dismissedPolicyCount: Int - dismissedPolicyCountNEQ: Int - dismissedPolicyCountIn: [Int!] - dismissedPolicyCountNotIn: [Int!] - dismissedPolicyCountGT: Int - dismissedPolicyCountGTE: Int - dismissedPolicyCountLT: Int - dismissedPolicyCountLTE: Int - - """policy_pending_low_count field predicates""" - policyPendingLowCount: Int - policyPendingLowCountNEQ: Int - policyPendingLowCountIn: [Int!] - policyPendingLowCountNotIn: [Int!] - policyPendingLowCountGT: Int - policyPendingLowCountGTE: Int - policyPendingLowCountLT: Int - policyPendingLowCountLTE: Int - - """policy_pending_medium_count field predicates""" - policyPendingMediumCount: Int - policyPendingMediumCountNEQ: Int - policyPendingMediumCountIn: [Int!] - policyPendingMediumCountNotIn: [Int!] - policyPendingMediumCountGT: Int - policyPendingMediumCountGTE: Int - policyPendingMediumCountLT: Int - policyPendingMediumCountLTE: Int - - """policy_pending_high_count field predicates""" - policyPendingHighCount: Int - policyPendingHighCountNEQ: Int - policyPendingHighCountIn: [Int!] - policyPendingHighCountNotIn: [Int!] - policyPendingHighCountGT: Int - policyPendingHighCountGTE: Int - policyPendingHighCountLT: Int - policyPendingHighCountLTE: Int - - """policy_pending_critical_count field predicates""" - policyPendingCriticalCount: Int - policyPendingCriticalCountNEQ: Int - policyPendingCriticalCountIn: [Int!] - policyPendingCriticalCountNotIn: [Int!] - policyPendingCriticalCountGT: Int - policyPendingCriticalCountGTE: Int - policyPendingCriticalCountLT: Int - policyPendingCriticalCountLTE: Int - - """estimated_volume_reduction_per_hour field predicates""" - estimatedVolumeReductionPerHour: Float - estimatedVolumeReductionPerHourNEQ: Float - estimatedVolumeReductionPerHourIn: [Float!] - estimatedVolumeReductionPerHourNotIn: [Float!] - estimatedVolumeReductionPerHourGT: Float - estimatedVolumeReductionPerHourGTE: Float - estimatedVolumeReductionPerHourLT: Float - estimatedVolumeReductionPerHourLTE: Float - estimatedVolumeReductionPerHourIsNil: Boolean - estimatedVolumeReductionPerHourNotNil: Boolean - - """estimated_bytes_reduction_per_hour field predicates""" - estimatedBytesReductionPerHour: Float - estimatedBytesReductionPerHourNEQ: Float - estimatedBytesReductionPerHourIn: [Float!] - estimatedBytesReductionPerHourNotIn: [Float!] - estimatedBytesReductionPerHourGT: Float - estimatedBytesReductionPerHourGTE: Float - estimatedBytesReductionPerHourLT: Float - estimatedBytesReductionPerHourLTE: Float - estimatedBytesReductionPerHourIsNil: Boolean - estimatedBytesReductionPerHourNotNil: Boolean - - """volume_per_hour field predicates""" - volumePerHour: Float - volumePerHourNEQ: Float - volumePerHourIn: [Float!] - volumePerHourNotIn: [Float!] - volumePerHourGT: Float - volumePerHourGTE: Float - volumePerHourLT: Float - volumePerHourLTE: Float - volumePerHourIsNil: Boolean - volumePerHourNotNil: Boolean - - """bytes_per_hour field predicates""" - bytesPerHour: Float - bytesPerHourNEQ: Float - bytesPerHourIn: [Float!] - bytesPerHourNotIn: [Float!] - bytesPerHourGT: Float - bytesPerHourGTE: Float - bytesPerHourLT: Float - bytesPerHourLTE: Float - bytesPerHourIsNil: Boolean - bytesPerHourNotNil: Boolean - - """cost_per_hour_bytes_usd field predicates""" - costPerHourBytesUsd: Float - costPerHourBytesUsdNEQ: Float - costPerHourBytesUsdIn: [Float!] - costPerHourBytesUsdNotIn: [Float!] - costPerHourBytesUsdGT: Float - costPerHourBytesUsdGTE: Float - costPerHourBytesUsdLT: Float - costPerHourBytesUsdLTE: Float - costPerHourBytesUsdIsNil: Boolean - costPerHourBytesUsdNotNil: Boolean - - """cost_per_hour_volume_usd field predicates""" - costPerHourVolumeUsd: Float - costPerHourVolumeUsdNEQ: Float - costPerHourVolumeUsdIn: [Float!] - costPerHourVolumeUsdNotIn: [Float!] - costPerHourVolumeUsdGT: Float - costPerHourVolumeUsdGTE: Float - costPerHourVolumeUsdLT: Float - costPerHourVolumeUsdLTE: Float - costPerHourVolumeUsdIsNil: Boolean - costPerHourVolumeUsdNotNil: Boolean - - """cost_per_hour_usd field predicates""" - costPerHourUsd: Float - costPerHourUsdNEQ: Float - costPerHourUsdIn: [Float!] - costPerHourUsdNotIn: [Float!] - costPerHourUsdGT: Float - costPerHourUsdGTE: Float - costPerHourUsdLT: Float - costPerHourUsdLTE: Float - costPerHourUsdIsNil: Boolean - costPerHourUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_bytes_usd field predicates""" - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourBytesUsdNEQ: Float - estimatedCostReductionPerHourBytesUsdIn: [Float!] - estimatedCostReductionPerHourBytesUsdNotIn: [Float!] - estimatedCostReductionPerHourBytesUsdGT: Float - estimatedCostReductionPerHourBytesUsdGTE: Float - estimatedCostReductionPerHourBytesUsdLT: Float - estimatedCostReductionPerHourBytesUsdLTE: Float - estimatedCostReductionPerHourBytesUsdIsNil: Boolean - estimatedCostReductionPerHourBytesUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_volume_usd field predicates""" - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourVolumeUsdNEQ: Float - estimatedCostReductionPerHourVolumeUsdIn: [Float!] - estimatedCostReductionPerHourVolumeUsdNotIn: [Float!] - estimatedCostReductionPerHourVolumeUsdGT: Float - estimatedCostReductionPerHourVolumeUsdGTE: Float - estimatedCostReductionPerHourVolumeUsdLT: Float - estimatedCostReductionPerHourVolumeUsdLTE: Float - estimatedCostReductionPerHourVolumeUsdIsNil: Boolean - estimatedCostReductionPerHourVolumeUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_usd field predicates""" - estimatedCostReductionPerHourUsd: Float - estimatedCostReductionPerHourUsdNEQ: Float - estimatedCostReductionPerHourUsdIn: [Float!] - estimatedCostReductionPerHourUsdNotIn: [Float!] - estimatedCostReductionPerHourUsdGT: Float - estimatedCostReductionPerHourUsdGTE: Float - estimatedCostReductionPerHourUsdLT: Float - estimatedCostReductionPerHourUsdLTE: Float - estimatedCostReductionPerHourUsdIsNil: Boolean - estimatedCostReductionPerHourUsdNotNil: Boolean - - """observed_volume_per_hour_before field predicates""" - observedVolumePerHourBefore: Float - observedVolumePerHourBeforeNEQ: Float - observedVolumePerHourBeforeIn: [Float!] - observedVolumePerHourBeforeNotIn: [Float!] - observedVolumePerHourBeforeGT: Float - observedVolumePerHourBeforeGTE: Float - observedVolumePerHourBeforeLT: Float - observedVolumePerHourBeforeLTE: Float - observedVolumePerHourBeforeIsNil: Boolean - observedVolumePerHourBeforeNotNil: Boolean - - """observed_volume_per_hour_after field predicates""" - observedVolumePerHourAfter: Float - observedVolumePerHourAfterNEQ: Float - observedVolumePerHourAfterIn: [Float!] - observedVolumePerHourAfterNotIn: [Float!] - observedVolumePerHourAfterGT: Float - observedVolumePerHourAfterGTE: Float - observedVolumePerHourAfterLT: Float - observedVolumePerHourAfterLTE: Float - observedVolumePerHourAfterIsNil: Boolean - observedVolumePerHourAfterNotNil: Boolean - - """observed_bytes_per_hour_before field predicates""" - observedBytesPerHourBefore: Float - observedBytesPerHourBeforeNEQ: Float - observedBytesPerHourBeforeIn: [Float!] - observedBytesPerHourBeforeNotIn: [Float!] - observedBytesPerHourBeforeGT: Float - observedBytesPerHourBeforeGTE: Float - observedBytesPerHourBeforeLT: Float - observedBytesPerHourBeforeLTE: Float - observedBytesPerHourBeforeIsNil: Boolean - observedBytesPerHourBeforeNotNil: Boolean - - """observed_bytes_per_hour_after field predicates""" - observedBytesPerHourAfter: Float - observedBytesPerHourAfterNEQ: Float - observedBytesPerHourAfterIn: [Float!] - observedBytesPerHourAfterNotIn: [Float!] - observedBytesPerHourAfterGT: Float - observedBytesPerHourAfterGTE: Float - observedBytesPerHourAfterLT: Float - observedBytesPerHourAfterLTE: Float - observedBytesPerHourAfterIsNil: Boolean - observedBytesPerHourAfterNotNil: Boolean - - """observed_cost_per_hour_before_bytes_usd field predicates""" - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeBytesUsdNEQ: Float - observedCostPerHourBeforeBytesUsdIn: [Float!] - observedCostPerHourBeforeBytesUsdNotIn: [Float!] - observedCostPerHourBeforeBytesUsdGT: Float - observedCostPerHourBeforeBytesUsdGTE: Float - observedCostPerHourBeforeBytesUsdLT: Float - observedCostPerHourBeforeBytesUsdLTE: Float - observedCostPerHourBeforeBytesUsdIsNil: Boolean - observedCostPerHourBeforeBytesUsdNotNil: Boolean - - """observed_cost_per_hour_before_volume_usd field predicates""" - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeVolumeUsdNEQ: Float - observedCostPerHourBeforeVolumeUsdIn: [Float!] - observedCostPerHourBeforeVolumeUsdNotIn: [Float!] - observedCostPerHourBeforeVolumeUsdGT: Float - observedCostPerHourBeforeVolumeUsdGTE: Float - observedCostPerHourBeforeVolumeUsdLT: Float - observedCostPerHourBeforeVolumeUsdLTE: Float - observedCostPerHourBeforeVolumeUsdIsNil: Boolean - observedCostPerHourBeforeVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_before_usd field predicates""" - observedCostPerHourBeforeUsd: Float - observedCostPerHourBeforeUsdNEQ: Float - observedCostPerHourBeforeUsdIn: [Float!] - observedCostPerHourBeforeUsdNotIn: [Float!] - observedCostPerHourBeforeUsdGT: Float - observedCostPerHourBeforeUsdGTE: Float - observedCostPerHourBeforeUsdLT: Float - observedCostPerHourBeforeUsdLTE: Float - observedCostPerHourBeforeUsdIsNil: Boolean - observedCostPerHourBeforeUsdNotNil: Boolean - - """observed_cost_per_hour_after_bytes_usd field predicates""" - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterBytesUsdNEQ: Float - observedCostPerHourAfterBytesUsdIn: [Float!] - observedCostPerHourAfterBytesUsdNotIn: [Float!] - observedCostPerHourAfterBytesUsdGT: Float - observedCostPerHourAfterBytesUsdGTE: Float - observedCostPerHourAfterBytesUsdLT: Float - observedCostPerHourAfterBytesUsdLTE: Float - observedCostPerHourAfterBytesUsdIsNil: Boolean - observedCostPerHourAfterBytesUsdNotNil: Boolean - - """observed_cost_per_hour_after_volume_usd field predicates""" - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterVolumeUsdNEQ: Float - observedCostPerHourAfterVolumeUsdIn: [Float!] - observedCostPerHourAfterVolumeUsdNotIn: [Float!] - observedCostPerHourAfterVolumeUsdGT: Float - observedCostPerHourAfterVolumeUsdGTE: Float - observedCostPerHourAfterVolumeUsdLT: Float - observedCostPerHourAfterVolumeUsdLTE: Float - observedCostPerHourAfterVolumeUsdIsNil: Boolean - observedCostPerHourAfterVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_after_usd field predicates""" - observedCostPerHourAfterUsd: Float - observedCostPerHourAfterUsdNEQ: Float - observedCostPerHourAfterUsdIn: [Float!] - observedCostPerHourAfterUsdNotIn: [Float!] - observedCostPerHourAfterUsdGT: Float - observedCostPerHourAfterUsdGTE: Float - observedCostPerHourAfterUsdLT: Float - observedCostPerHourAfterUsdLTE: Float - observedCostPerHourAfterUsdIsNil: Boolean - observedCostPerHourAfterUsdNotNil: Boolean - - """refreshed_at field predicates""" - refreshedAt: Time - refreshedAtNEQ: Time - refreshedAtIn: [Time!] - refreshedAtNotIn: [Time!] - refreshedAtGT: Time - refreshedAtGTE: Time - refreshedAtLT: Time - refreshedAtLTE: Time -} - """ LogEventWhereInput is used for filtering LogEvent objects. Input was generated by ent. @@ -3888,8 +2420,9 @@ input LogEventWhereInput { not: LogEventWhereInput and: [LogEventWhereInput!] or: [LogEventWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -3898,8 +2431,9 @@ input LogEventWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -3908,14 +2442,16 @@ input LogEventWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """service_id field predicates""" + """ + service_id field predicates + """ serviceID: ID serviceIDNEQ: ID serviceIDIn: [ID!] serviceIDNotIn: [ID!] - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -3929,8 +2465,9 @@ input LogEventWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """description field predicates""" + """ + description field predicates + """ description: String descriptionNEQ: String descriptionIn: [String!] @@ -3944,32 +2481,18 @@ input LogEventWhereInput { descriptionHasSuffix: String descriptionEqualFold: String descriptionContainsFold: String - - """severity field predicates""" + """ + severity field predicates + """ severity: LogEventSeverity severityNEQ: LogEventSeverity severityIn: [LogEventSeverity!] severityNotIn: [LogEventSeverity!] severityIsNil: Boolean severityNotNil: Boolean - - """signal_purpose field predicates""" - signalPurpose: LogEventSignalPurpose - signalPurposeNEQ: LogEventSignalPurpose - signalPurposeIn: [LogEventSignalPurpose!] - signalPurposeNotIn: [LogEventSignalPurpose!] - - """event_nature field predicates""" - eventNature: LogEventEventNature - eventNatureNEQ: LogEventEventNature - eventNatureIn: [LogEventEventNature!] - eventNatureNotIn: [LogEventEventNature!] - - """is_fragment field predicates""" - isFragment: Boolean - isFragmentNEQ: Boolean - - """baseline_volume_per_hour field predicates""" + """ + baseline_volume_per_hour field predicates + """ baselineVolumePerHour: Float baselineVolumePerHourNEQ: Float baselineVolumePerHourIn: [Float!] @@ -3980,8 +2503,9 @@ input LogEventWhereInput { baselineVolumePerHourLTE: Float baselineVolumePerHourIsNil: Boolean baselineVolumePerHourNotNil: Boolean - - """baseline_avg_bytes field predicates""" + """ + baseline_avg_bytes field predicates + """ baselineAvgBytes: Float baselineAvgBytesNEQ: Float baselineAvgBytesIn: [Float!] @@ -3992,205 +2516,9 @@ input LogEventWhereInput { baselineAvgBytesLTE: Float baselineAvgBytesIsNil: Boolean baselineAvgBytesNotNil: Boolean - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """updated_at field predicates""" - updatedAt: Time - updatedAtNEQ: Time - updatedAtIn: [Time!] - updatedAtNotIn: [Time!] - updatedAtGT: Time - updatedAtGTE: Time - updatedAtLT: Time - updatedAtLTE: Time - - """service edge predicates""" - hasService: Boolean - hasServiceWith: [ServiceWhereInput!] - - """log_sample edge predicates""" - hasLogSample: Boolean - hasLogSampleWith: [LogSampleWhereInput!] - - """policies edge predicates""" - hasPolicies: Boolean - hasPoliciesWith: [LogEventPolicyWhereInput!] - - """log_event_fields edge predicates""" - hasLogEventFields: Boolean - hasLogEventFieldsWith: [LogEventFieldWhereInput!] -} - -"""A normalized log record following OTEL conventions""" -type LogRecord { - """When the log was recorded""" - timestamp: Time! - - """The log message body""" - body: String! - - """Severity level (e.g., INFO, ERROR, DEBUG)""" - severityText: String - - """Log-level attributes""" - attributes: Map - - """Resource-level attributes (e.g., service.name, host.name)""" - resourceAttributes: Map - - """Scope-level attributes (e.g., instrumentation library info)""" - scopeAttributes: Map -} - -type LogSample implements Node { - """Unique identifier of the log sample""" - id: ID! - - """Parent account for tenant isolation""" - accountID: ID! - - """Service these logs belong to""" - serviceID: ID! - - """Datadog account that produced this sample""" - datadogAccountID: ID! - - """Number of OTEL log records in this page""" - recordCount: Int! - - """Size of the GCS object in bytes""" - byteSize: Int! - - """GCS object path for the log payload""" - storagePath: String! - - """Earliest log timestamp in this page""" - timeFrom: Time! - - """Latest log timestamp in this page""" - timeTo: Time! - - """When this sample was ingested""" - createdAt: Time! - - """When this record was last updated""" - updatedAt: Time! - - """Account this sample belongs to""" - account: Account! - - """Service these logs belong to""" - service: Service! - - """Datadog account that produced this sample""" - datadogAccount: DatadogAccount! - - """Log events discovered from this sample""" - logEvents: [LogEvent!] -} - -""" -LogSampleWhereInput is used for filtering LogSample objects. -Input was generated by ent. -""" -input LogSampleWhereInput { - not: LogSampleWhereInput - and: [LogSampleWhereInput!] - or: [LogSampleWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: ID - accountIDNEQ: ID - accountIDIn: [ID!] - accountIDNotIn: [ID!] - - """service_id field predicates""" - serviceID: ID - serviceIDNEQ: ID - serviceIDIn: [ID!] - serviceIDNotIn: [ID!] - - """datadog_account_id field predicates""" - datadogAccountID: ID - datadogAccountIDNEQ: ID - datadogAccountIDIn: [ID!] - datadogAccountIDNotIn: [ID!] - - """record_count field predicates""" - recordCount: Int - recordCountNEQ: Int - recordCountIn: [Int!] - recordCountNotIn: [Int!] - recordCountGT: Int - recordCountGTE: Int - recordCountLT: Int - recordCountLTE: Int - - """byte_size field predicates""" - byteSize: Int - byteSizeNEQ: Int - byteSizeIn: [Int!] - byteSizeNotIn: [Int!] - byteSizeGT: Int - byteSizeGTE: Int - byteSizeLT: Int - byteSizeLTE: Int - - """storage_path field predicates""" - storagePath: String - storagePathNEQ: String - storagePathIn: [String!] - storagePathNotIn: [String!] - storagePathGT: String - storagePathGTE: String - storagePathLT: String - storagePathLTE: String - storagePathContains: String - storagePathHasPrefix: String - storagePathHasSuffix: String - storagePathEqualFold: String - storagePathContainsFold: String - - """time_from field predicates""" - timeFrom: Time - timeFromNEQ: Time - timeFromIn: [Time!] - timeFromNotIn: [Time!] - timeFromGT: Time - timeFromGTE: Time - timeFromLT: Time - timeFromLTE: Time - - """time_to field predicates""" - timeTo: Time - timeToNEQ: Time - timeToIn: [Time!] - timeToNotIn: [Time!] - timeToGT: Time - timeToGTE: Time - timeToLT: Time - timeToLTE: Time - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -4199,8 +2527,9 @@ input LogSampleWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -4209,112 +2538,123 @@ input LogSampleWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """account edge predicates""" - hasAccount: Boolean - hasAccountWith: [AccountWhereInput!] - - """service edge predicates""" + """ + service edge predicates + """ hasService: Boolean hasServiceWith: [ServiceWhereInput!] - - """datadog_account edge predicates""" - hasDatadogAccount: Boolean - hasDatadogAccountWith: [DatadogAccountWhereInput!] - - """log_events edge predicates""" - hasLogEvents: Boolean - hasLogEventsWith: [LogEventWhereInput!] + """ + facts edge predicates + """ + hasFacts: Boolean + hasFactsWith: [LogEventFactWhereInput!] + """ + log_event_policies edge predicates + """ + hasLogEventPolicies: Boolean + hasLogEventPoliciesWith: [LogEventPolicyWhereInput!] } - +""" +The builtin Map type +""" scalar Map - type Message implements Node { - """Unique identifier""" + """ + Unique identifier + """ id: ID! - """ Denormalized for tenant isolation. Auto-set via trigger from conversation.account_id. """ accountID: UUID! - - """Conversation this message belongs to""" + """ + Conversation this message belongs to + """ conversationID: ID! - """ - Who sent this message. user: human-originated, assistant: AI-originated. + Who sent this message. Values: user = Human-originated message content.; assistant = AI-originated message content. """ role: MessageRole! - """ - Why the assistant stopped generating. end_turn: completed response, tool_use: - paused to call a tool. Null for user messages. + Why the assistant stopped generating. Null for user messages. Values: end_turn = The assistant completed its response.; tool_use = The assistant paused to call a tool. """ stopReason: MessageStopReason - - """AI model that produced this message. Null for user messages.""" + """ + AI model that produced this message. Null for user messages. + """ model: String - - """When the message was created""" + """ + When the message was created + """ createdAt: Time! - - """Conversation this message belongs to""" + """ + Conversation this message belongs to + """ conversation: Conversation! - - """Views created by this message via show_view tool calls""" - views: [View!] - - """Array of typed content blocks: text, thinking, tool_use, tool_result.""" - content: [ContentBlock!]! } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type MessageConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [MessageEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type MessageEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: Message - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for Message connections""" +""" +Ordering options for Message connections +""" input MessageOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order Messages.""" + """ + The field by which to order Messages. + """ field: MessageOrderField! } - -"""Properties by which Message connections can be ordered.""" +""" +Properties by which Message connections can be ordered. +""" enum MessageOrderField { CREATED_AT } - -"""MessageRole is enum for the field role""" -enum MessageRole { +""" +MessageRole is enum for the field role +""" +enum MessageRole @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/message.Role") { user assistant } - -"""MessageStopReason is enum for the field stop_reason""" -enum MessageStopReason { +""" +MessageStopReason is enum for the field stop_reason +""" +enum MessageStopReason @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/message.StopReason") { end_turn tool_use } - """ MessageWhereInput is used for filtering Message objects. Input was generated by ent. @@ -4323,8 +2663,9 @@ input MessageWhereInput { not: MessageWhereInput and: [MessageWhereInput!] or: [MessageWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -4333,8 +2674,9 @@ input MessageWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -4343,28 +2685,32 @@ input MessageWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """conversation_id field predicates""" + """ + conversation_id field predicates + """ conversationID: ID conversationIDNEQ: ID conversationIDIn: [ID!] conversationIDNotIn: [ID!] - - """role field predicates""" + """ + role field predicates + """ role: MessageRole roleNEQ: MessageRole roleIn: [MessageRole!] roleNotIn: [MessageRole!] - - """stop_reason field predicates""" + """ + stop_reason field predicates + """ stopReason: MessageStopReason stopReasonNEQ: MessageStopReason stopReasonIn: [MessageStopReason!] stopReasonNotIn: [MessageStopReason!] stopReasonIsNil: Boolean stopReasonNotNil: Boolean - - """model field predicates""" + """ + model field predicates + """ model: String modelNEQ: String modelIn: [String!] @@ -4380,8 +2726,9 @@ input MessageWhereInput { modelNotNil: Boolean modelEqualFold: String modelContainsFold: String - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -4390,178 +2737,262 @@ input MessageWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """conversation edge predicates""" - hasConversation: Boolean - hasConversationWith: [ConversationWhereInput!] - - """views edge predicates""" - hasViews: Boolean - hasViewsWith: [ViewWhereInput!] -} - -type Mutation { - createAccount(input: CreateAccountInput!): Account! - updateAccount(id: ID!, input: UpdateAccountInput!): Account! - deleteAccount(id: ID!): Boolean! - createOrganization(input: CreateOrganizationInput!): Organization! - createOrganizationAndBootstrap(input: CreateOrganizationInput!): OrganizationBootstrapResult! - updateOrganization(id: ID!, input: UpdateOrganizationInput!): Organization! - deleteOrganization(id: ID!, confirmed: Boolean!): Boolean! - createDatadogAccount(input: CreateDatadogAccountWithCredentialsInput!): DatadogAccount! - updateDatadogAccount(id: ID!, input: UpdateDatadogAccountInput!): DatadogAccount! - deleteDatadogAccount(id: ID!): Boolean! - validateDatadogApiKey(input: ValidateDatadogApiKeyInput!): ValidateDatadogApiKeyResult! - createWorkspace(input: CreateWorkspaceInput!): Workspace! - updateWorkspace(id: ID!, input: UpdateWorkspaceInput!): Workspace! - deleteWorkspace(id: ID!): Boolean! - createTeam(input: CreateTeamInput!): Team! - updateTeam(id: ID!, input: UpdateTeamInput!): Team! - deleteTeam(id: ID!): Boolean! - updateService(id: ID!, input: UpdateServiceInput!): Service! - - """ - Create a new API key for edge instance authentication. - Returns the plain key only once - it cannot be retrieved afterward. - """ - createEdgeApiKey(input: CreateEdgeApiKeyInput!): CreateEdgeApiKeyResult! - - """ - Revoke an edge API key. The key can no longer be used for authentication. - """ - revokeEdgeApiKey(id: ID!): EdgeApiKey! - - """ - Create a new conversation in a workspace. - The conversation is owned by the authenticated user. - """ - createConversation(input: CreateConversationInput!): Conversation! - - """Update a conversation (e.g., set title).""" - updateConversation(id: ID!, input: UpdateConversationInput!): Conversation! - - """Delete a conversation and all its messages.""" - deleteConversation(id: ID!): Boolean! - - """Create a new message in a conversation.""" - createMessage(input: CreateMessageInput!): Message! - - """ - Create a new view. Called by clients when executing show_view tool results. - If the conversation has view_id set (iteration mode), forked_from_id is auto-set. - """ - createView(input: CreateViewInput!): View! - - """ - Add a view to the user's favorites. - Idempotent: if already favorited, returns the existing favorite. - """ - createViewFavorite(input: CreateViewFavoriteInput!): ViewFavorite! - - """Remove a view from the user's favorites.""" - deleteViewFavorite(viewId: ID!): Boolean! - - """ - Approve a log event policy, enabling it for enforcement. - Clears any previous dismissal. - """ - approveLogEventPolicy(id: ID!): LogEventPolicy! - - """ - Dismiss a log event policy, hiding it from pending review. - Clears any previous approval. - """ - dismissLogEventPolicy(id: ID!): LogEventPolicy! - """ - Reset a log event policy to pending, clearing any approval or dismissal. + conversation edge predicates """ - resetLogEventPolicy(id: ID!): LogEventPolicy! + hasConversation: Boolean + hasConversationWith: [ConversationWhereInput!] } - """ An object with an ID. Follows the [Relay Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm) """ -interface Node { - """The id of the object.""" +interface Node @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres.Noder") { + """ + The id of the object. + """ id: ID! } - """ Possible directions in which to order a list of items when provided an `orderBy` argument. """ enum OrderDirection { - """Specifies an ascending order for a given `orderBy` argument.""" + """ + Specifies an ascending order for a given `orderBy` argument. + """ ASC - - """Specifies a descending order for a given `orderBy` argument.""" + """ + Specifies a descending order for a given `orderBy` argument. + """ DESC } - type Organization implements Node { - """Unique identifier of the organization""" + """ + Unique identifier of the organization + """ id: ID! - - """Human-readable name, unique across the system""" + """ + Human-readable name, unique across the system + """ name: String! - - """WorkOS organization ID for client-side auth token scoping""" + """ + WorkOS organization ID for client-side auth token scoping + """ workosOrganizationID: String! - - """When the organization was created""" + """ + When the organization was created + """ createdAt: Time! - - """When the organization was last updated""" + """ + When the organization was last updated + """ updatedAt: Time! - - """Accounts belonging to this organization""" + """ + Accounts belonging to this organization + """ accounts: [Account!] + """ + Teams belonging to this organization + """ + teams: [Team!] + """ + Team memberships in this organization + """ + teamMemberships: [TeamMembership!] + """ + Extensible typed facts attached to this organization + """ + facts: [OrganizationFact!] } - -type OrganizationBootstrapResult { - organization: Organization! - account: Account! - workspace: Workspace! -} - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type OrganizationConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [OrganizationEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type OrganizationEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: Organization - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for Organization connections""" +type OrganizationFact implements Node { + """ + Unique identifier + """ + id: ID! + """ + The organization this fact belongs to + """ + organizationID: ID! + """ + Fact namespace, e.g. semantic or operational. + """ + namespace: String! + """ + Fact type within the namespace, e.g. organization_profile or operational_priorities. + """ + factType: String! + """ + When this fact was first recorded + """ + createdAt: Time! + """ + When this fact was last refreshed + """ + updatedAt: Time! + """ + The organization this fact belongs to + """ + organization: Organization! +} +""" +Ordering options for OrganizationFact connections +""" +input OrganizationFactOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order OrganizationFacts. + """ + field: OrganizationFactOrderField! +} +""" +Properties by which OrganizationFact connections can be ordered. +""" +enum OrganizationFactOrderField { + NAMESPACE + FACT_TYPE + CREATED_AT + UPDATED_AT +} +""" +OrganizationFactWhereInput is used for filtering OrganizationFact objects. +Input was generated by ent. +""" +input OrganizationFactWhereInput { + not: OrganizationFactWhereInput + and: [OrganizationFactWhereInput!] + or: [OrganizationFactWhereInput!] + """ + id field predicates + """ + id: ID + idNEQ: ID + idIn: [ID!] + idNotIn: [ID!] + idGT: ID + idGTE: ID + idLT: ID + idLTE: ID + """ + organization_id field predicates + """ + organizationID: ID + organizationIDNEQ: ID + organizationIDIn: [ID!] + organizationIDNotIn: [ID!] + """ + namespace field predicates + """ + namespace: String + namespaceNEQ: String + namespaceIn: [String!] + namespaceNotIn: [String!] + namespaceGT: String + namespaceGTE: String + namespaceLT: String + namespaceLTE: String + namespaceContains: String + namespaceHasPrefix: String + namespaceHasSuffix: String + namespaceEqualFold: String + namespaceContainsFold: String + """ + fact_type field predicates + """ + factType: String + factTypeNEQ: String + factTypeIn: [String!] + factTypeNotIn: [String!] + factTypeGT: String + factTypeGTE: String + factTypeLT: String + factTypeLTE: String + factTypeContains: String + factTypeHasPrefix: String + factTypeHasSuffix: String + factTypeEqualFold: String + factTypeContainsFold: String + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + organization edge predicates + """ + hasOrganization: Boolean + hasOrganizationWith: [OrganizationWhereInput!] +} +""" +Ordering options for Organization connections +""" input OrganizationOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order Organizations.""" + """ + The field by which to order Organizations. + """ field: OrganizationOrderField! } - -"""Properties by which Organization connections can be ordered.""" +""" +Properties by which Organization connections can be ordered. +""" enum OrganizationOrderField { NAME CREATED_AT UPDATED_AT } - """ OrganizationWhereInput is used for filtering Organization objects. Input was generated by ent. @@ -4570,8 +3001,9 @@ input OrganizationWhereInput { not: OrganizationWhereInput and: [OrganizationWhereInput!] or: [OrganizationWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -4580,8 +3012,9 @@ input OrganizationWhereInput { idGTE: ID idLT: ID idLTE: ID - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -4595,8 +3028,9 @@ input OrganizationWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -4605,8 +3039,9 @@ input OrganizationWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -4615,51 +3050,80 @@ input OrganizationWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """accounts edge predicates""" + """ + accounts edge predicates + """ hasAccounts: Boolean hasAccountsWith: [AccountWhereInput!] + """ + teams edge predicates + """ + hasTeams: Boolean + hasTeamsWith: [TeamWhereInput!] + """ + team_memberships edge predicates + """ + hasTeamMemberships: Boolean + hasTeamMembershipsWith: [TeamMembershipWhereInput!] + """ + facts edge predicates + """ + hasFacts: Boolean + hasFactsWith: [OrganizationFactWhereInput!] } - """ Information about pagination in a connection. https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo """ type PageInfo { - """When paginating forwards, are there more items?""" + """ + When paginating forwards, are there more items? + """ hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" + """ + When paginating backwards, are there more items? + """ hasPreviousPage: Boolean! - - """When paginating backwards, the cursor to continue.""" + """ + When paginating backwards, the cursor to continue. + """ startCursor: Cursor - - """When paginating forwards, the cursor to continue.""" + """ + When paginating forwards, the cursor to continue. + """ endCursor: Cursor } - type Query { - """Fetches an object given its ID.""" + """ + Fetches an object given its ID. + """ node( - """ID of the object.""" + """ + ID of the object. + """ id: ID! ): Node - - """Lookup nodes by a list of IDs.""" + """ + Lookup nodes by a list of IDs. + """ nodes( - """The list of node IDs.""" + """ + The list of node IDs. + """ ids: [ID!]! ): [Node]! - """ Query accounts. Accounts belong to an organization and contain services and workspaces. """ accounts( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4667,45 +3131,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Accounts returned from the connection.""" + """ + Ordering options for Accounts returned from the connection. + """ orderBy: AccountOrder - """Filtering options for Accounts returned from the connection.""" + """ + Filtering options for Accounts returned from the connection. + """ where: AccountWhereInput ): AccountConnection! - - """Query conversations.""" + """ + Query conversations. + """ conversations( - """Returns the elements in the list that come after the specified cursor.""" - after: Cursor - - """Returns the first _n_ elements from the list.""" - first: Int - """ - Returns the elements in the list that come before the specified cursor. + Returns the elements in the list that come after the specified cursor. """ - before: Cursor - - """Returns the last _n_ elements from the list.""" - last: Int - - """Ordering options for Conversations returned from the connection.""" - orderBy: ConversationOrder - - """Filtering options for Conversations returned from the connection.""" - where: ConversationWhereInput - ): ConversationConnection! - - """Query active context entities in conversations.""" - conversationContexts( - """Returns the elements in the list that come after the specified cursor.""" after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4713,26 +3165,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int """ - Ordering options for ConversationContexts returned from the connection. + Ordering options for Conversations returned from the connection. """ - orderBy: ConversationContextOrder + orderBy: ConversationOrder """ - Filtering options for ConversationContexts returned from the connection. + Filtering options for Conversations returned from the connection. """ - where: ConversationContextWhereInput - ): ConversationContextConnection! - - """Query connected Datadog accounts.""" + where: ConversationWhereInput + ): ConversationConnection! + """ + Query connected Datadog accounts. + """ datadogAccounts( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4740,22 +3199,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for DatadogAccounts returned from the connection.""" + """ + Ordering options for DatadogAccounts returned from the connection. + """ orderBy: DatadogAccountOrder - """Filtering options for DatadogAccounts returned from the connection.""" + """ + Filtering options for DatadogAccounts returned from the connection. + """ where: DatadogAccountWhereInput ): DatadogAccountConnection! - - """Query API keys for edge instance authentication.""" + """ + Query API keys for edge instance authentication. + """ edgeAPIKeys( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4763,22 +3233,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for EdgeApiKeys returned from the connection.""" + """ + Ordering options for EdgeApiKeys returned from the connection. + """ orderBy: EdgeApiKeyOrder - """Filtering options for EdgeApiKeys returned from the connection.""" + """ + Filtering options for EdgeApiKeys returned from the connection. + """ where: EdgeApiKeyWhereInput ): EdgeApiKeyConnection! - - """Query edge instances that sync policies from the control plane.""" + """ + Query edge instances that sync policies from the control plane. + """ edgeInstances( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4786,24 +3267,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for EdgeInstances returned from the connection.""" + """ + Ordering options for EdgeInstances returned from the connection. + """ orderBy: EdgeInstanceOrder - """Filtering options for EdgeInstances returned from the connection.""" + """ + Filtering options for EdgeInstances returned from the connection. + """ where: EdgeInstanceWhereInput ): EdgeInstanceConnection! - """ - Query log events discovered in your services. Each log event is a distinct message pattern. + Query findings in your account. """ - logEvents( - """Returns the elements in the list that come after the specified cursor.""" + findings( + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4811,24 +3301,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for LogEvents returned from the connection.""" - orderBy: LogEventOrder - - """Filtering options for LogEvents returned from the connection.""" - where: LogEventWhereInput - ): LogEventConnection! + """ + Ordering options for Findings returned from the connection. + """ + orderBy: FindingOrder + """ + Filtering options for Findings returned from the connection. + """ + where: FindingWhereInput + ): FindingConnection! """ - Query log event policies. Each policy is a category-specific recommendation. + Query log events discovered in your services. Each log event is a distinct message pattern. """ - logEventPolicies( - """Returns the elements in the list that come after the specified cursor.""" + logEvents( + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4836,22 +3335,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for LogEventPolicies returned from the connection.""" - orderBy: LogEventPolicyOrder - - """Filtering options for LogEventPolicies returned from the connection.""" - where: LogEventPolicyWhereInput - ): LogEventPolicyConnection! + """ + Ordering options for LogEvents returned from the connection. + """ + orderBy: LogEventOrder - """Query messages in chat conversations.""" + """ + Filtering options for LogEvents returned from the connection. + """ + where: LogEventWhereInput + ): LogEventConnection! + """ + Query messages in chat conversations. + """ messages( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4859,24 +3369,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Messages returned from the connection.""" + """ + Ordering options for Messages returned from the connection. + """ orderBy: MessageOrder - """Filtering options for Messages returned from the connection.""" + """ + Filtering options for Messages returned from the connection. + """ where: MessageWhereInput ): MessageConnection! - """ Query organizations. An organization is the top-level container that holds accounts. """ organizations( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4884,22 +3403,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Organizations returned from the connection.""" + """ + Ordering options for Organizations returned from the connection. + """ orderBy: OrganizationOrder - """Filtering options for Organizations returned from the connection.""" + """ + Filtering options for Organizations returned from the connection. + """ where: OrganizationWhereInput ): OrganizationConnection! - - """Query services in your system.""" + """ + Query services in your system. + """ services( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4907,22 +3437,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Services returned from the connection.""" + """ + Ordering options for Services returned from the connection. + """ orderBy: ServiceOrder - """Filtering options for Services returned from the connection.""" + """ + Filtering options for Services returned from the connection. + """ where: ServiceWhereInput ): ServiceConnection! - - """Query teams in your organization.""" - teams( - """Returns the elements in the list that come after the specified cursor.""" + """ + Query service ownership mappings in the current account. + """ + serviceTeamMappings( + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4930,24 +3471,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Teams returned from the connection.""" - orderBy: TeamOrder - - """Filtering options for Teams returned from the connection.""" - where: TeamWhereInput - ): TeamConnection! + """ + Ordering options for ServiceTeamMappings returned from the connection. + """ + orderBy: ServiceTeamMappingOrder + """ + Filtering options for ServiceTeamMappings returned from the connection. + """ + where: ServiceTeamMappingWhereInput + ): ServiceTeamMappingConnection! """ - Query saved views. Views are immutable queries against catalog entities. + Query teams in your organization. """ - views( - """Returns the elements in the list that come after the specified cursor.""" + teams( + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4955,22 +3505,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Views returned from the connection.""" - orderBy: ViewOrder - - """Filtering options for Views returned from the connection.""" - where: ViewWhereInput - ): ViewConnection! + """ + Ordering options for Teams returned from the connection. + """ + orderBy: TeamOrder - """Query view favorites. Each favorite links a user to a saved view.""" - viewFavorites( - """Returns the elements in the list that come after the specified cursor.""" + """ + Filtering options for Teams returned from the connection. + """ + where: TeamWhereInput + ): TeamConnection! + """ + Query team memberships in your organization. + """ + teamMemberships( + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -4978,24 +3539,33 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for ViewFavorites returned from the connection.""" - orderBy: ViewFavoriteOrder - - """Filtering options for ViewFavorites returned from the connection.""" - where: ViewFavoriteWhereInput - ): ViewFavoriteConnection! + """ + Ordering options for TeamMemberships returned from the connection. + """ + orderBy: TeamMembershipOrder + """ + Filtering options for TeamMemberships returned from the connection. + """ + where: TeamMembershipWhereInput + ): TeamMembershipConnection! """ Query workspaces. Workspaces are used to analyze and classify telemetry. """ workspaces( - """Returns the elements in the list that come after the specified cursor.""" + """ + Returns the elements in the list that come after the specified cursor. + """ after: Cursor - """Returns the first _n_ elements from the list.""" + """ + Returns the first _n_ elements from the list. + """ first: Int """ @@ -5003,172 +3573,179 @@ type Query { """ before: Cursor - """Returns the last _n_ elements from the list.""" + """ + Returns the last _n_ elements from the list. + """ last: Int - """Ordering options for Workspaces returned from the connection.""" + """ + Ordering options for Workspaces returned from the connection. + """ orderBy: WorkspaceOrder - """Filtering options for Workspaces returned from the connection.""" + """ + Filtering options for Workspaces returned from the connection. + """ where: WorkspaceWhereInput ): WorkspaceConnection! - - """ - Returns the currently authenticated user. - Use this to check authentication status and organization assignment. - """ - viewer: Viewer! } - type Service implements Node { - """Unique identifier of the service""" + """ + Unique identifier of the service + """ id: ID! - - """Parent account this service belongs to""" + """ + Parent account this service belongs to + """ accountID: ID! - - """Service identifier in telemetry (e.g., 'checkout-service')""" + """ + Service identifier in telemetry (e.g., 'checkout-service') + """ name: String! - """ - AI-generated description of what this service does and its telemetry characteristics + Whether log analysis and policy generation is active for this service """ - description: String! - - """Whether log analysis and policy generation is active for this service""" enabled: Boolean! - """ - Approximate weekly log count from initial discovery (7-day period from Datadog) + Approximate weekly log count from initial catalog loop pass (7-day period from Datadog) """ initialWeeklyLogCount: Int - - """When the service was created""" + """ + When the service was created + """ createdAt: Time! - - """When the service was last updated""" + """ + When the service was last updated + """ updatedAt: Time! - - """Account this service belongs to""" + """ + Account this service belongs to + """ account: Account! - - """Log event types produced by this service""" + """ + Log event types produced by this service + """ logEvents: [LogEvent!] - """ - Status of this service from a specific Datadog account. - Shows where the service is in the discovery pipeline. - Returns null if cache has not been populated yet. + Typed service facts refreshed as one owned fact group. + """ + facts: [ServiceFact!] + """ + Findings owned by this service, including service-scoped and log-event-scoped findings. + """ + findings: [Finding!] """ - status(datadogAccountID: ID!): ServiceStatusCache + Owning team mapping for this service + """ + teamMapping: ServiceTeamMapping } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type ServiceConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [ServiceEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type ServiceEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: Service - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for Service connections""" -input ServiceOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order Services.""" - field: ServiceOrderField! -} - -"""Properties by which Service connections can be ordered.""" -enum ServiceOrderField { - NAME - ENABLED - INITIAL_WEEKLY_LOG_COUNT - CREATED_AT - UPDATED_AT -} - -type ServiceStatusCache implements Node { +type ServiceFact implements Node { + """ + Unique identifier + """ id: ID! - serviceID: ID! + """ + Denormalized for tenant isolation. Auto-set via trigger from service.account_id. + """ accountID: UUID! - datadogAccountID: ID! - """ - Overall health of the service. DISABLED (integration turned off), INACTIVE (no data received), OK (healthy). - """ - health: ServiceStatusCacheHealth! - logEventCount: Int! - logEventAnalyzedCount: Int! - policyPendingCount: Int! - policyApprovedCount: Int! - policyDismissedCount: Int! - policyPendingLowCount: Int! - policyPendingMediumCount: Int! - policyPendingHighCount: Int! - policyPendingCriticalCount: Int! - serviceVolumePerHour: Float - serviceDebugVolumePerHour: Float - serviceInfoVolumePerHour: Float - serviceWarnVolumePerHour: Float - serviceErrorVolumePerHour: Float - serviceOtherVolumePerHour: Float - serviceCostPerHourVolumeUsd: Float - logEventVolumePerHour: Float - logEventBytesPerHour: Float - logEventCostPerHourBytesUsd: Float - logEventCostPerHourVolumeUsd: Float - logEventCostPerHourUsd: Float - estimatedVolumeReductionPerHour: Float - estimatedBytesReductionPerHour: Float - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourUsd: Float - observedVolumePerHourBefore: Float - observedVolumePerHourAfter: Float - observedBytesPerHourBefore: Float - observedBytesPerHourAfter: Float - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeUsd: Float - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterUsd: Float - refreshedAt: Time! + The service this fact belongs to + """ + serviceID: ID! + """ + Owned fact group refreshed together, e.g. catalog_service_facts. + """ + factGroup: String! + """ + Current code version applied for this fact group. + """ + version: Int! + """ + Fact namespace, e.g. semantic or operational. + """ + namespace: String! + """ + Fact type within the namespace, e.g. service_profile or actionability_profile. + """ + factType: String! + """ + When this fact was first recorded + """ + createdAt: Time! + """ + When this fact was last refreshed + """ + updatedAt: Time! + """ + The service this fact belongs to + """ + service: Service! } - -"""ServiceStatusCacheHealth is enum for the field health""" -enum ServiceStatusCacheHealth { - DISABLED - INACTIVE - ERROR - OK +""" +Ordering options for ServiceFact connections +""" +input ServiceFactOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order ServiceFacts. + """ + field: ServiceFactOrderField! +} +""" +Properties by which ServiceFact connections can be ordered. +""" +enum ServiceFactOrderField { + FACT_GROUP + VERSION + NAMESPACE + FACT_TYPE + CREATED_AT + UPDATED_AT } - """ -ServiceStatusCacheWhereInput is used for filtering ServiceStatusCache objects. +ServiceFactWhereInput is used for filtering ServiceFact objects. Input was generated by ent. """ -input ServiceStatusCacheWhereInput { - not: ServiceStatusCacheWhereInput - and: [ServiceStatusCacheWhereInput!] - or: [ServiceStatusCacheWhereInput!] - - """id field predicates""" +input ServiceFactWhereInput { + not: ServiceFactWhereInput + and: [ServiceFactWhereInput!] + or: [ServiceFactWhereInput!] + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -5177,14 +3754,9 @@ input ServiceStatusCacheWhereInput { idGTE: ID idLT: ID idLTE: ID - - """service_id field predicates""" - serviceID: ID - serviceIDNEQ: ID - serviceIDIn: [ID!] - serviceIDNotIn: [ID!] - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: UUID accountIDNEQ: UUID accountIDIn: [UUID!] @@ -5193,444 +3765,284 @@ input ServiceStatusCacheWhereInput { accountIDGTE: UUID accountIDLT: UUID accountIDLTE: UUID - - """datadog_account_id field predicates""" - datadogAccountID: ID - datadogAccountIDNEQ: ID - datadogAccountIDIn: [ID!] - datadogAccountIDNotIn: [ID!] - - """health field predicates""" - health: ServiceStatusCacheHealth - healthNEQ: ServiceStatusCacheHealth - healthIn: [ServiceStatusCacheHealth!] - healthNotIn: [ServiceStatusCacheHealth!] - - """log_event_count field predicates""" - logEventCount: Int - logEventCountNEQ: Int - logEventCountIn: [Int!] - logEventCountNotIn: [Int!] - logEventCountGT: Int - logEventCountGTE: Int - logEventCountLT: Int - logEventCountLTE: Int - - """log_event_analyzed_count field predicates""" - logEventAnalyzedCount: Int - logEventAnalyzedCountNEQ: Int - logEventAnalyzedCountIn: [Int!] - logEventAnalyzedCountNotIn: [Int!] - logEventAnalyzedCountGT: Int - logEventAnalyzedCountGTE: Int - logEventAnalyzedCountLT: Int - logEventAnalyzedCountLTE: Int - - """policy_pending_count field predicates""" - policyPendingCount: Int - policyPendingCountNEQ: Int - policyPendingCountIn: [Int!] - policyPendingCountNotIn: [Int!] - policyPendingCountGT: Int - policyPendingCountGTE: Int - policyPendingCountLT: Int - policyPendingCountLTE: Int - - """policy_approved_count field predicates""" - policyApprovedCount: Int - policyApprovedCountNEQ: Int - policyApprovedCountIn: [Int!] - policyApprovedCountNotIn: [Int!] - policyApprovedCountGT: Int - policyApprovedCountGTE: Int - policyApprovedCountLT: Int - policyApprovedCountLTE: Int - - """policy_dismissed_count field predicates""" - policyDismissedCount: Int - policyDismissedCountNEQ: Int - policyDismissedCountIn: [Int!] - policyDismissedCountNotIn: [Int!] - policyDismissedCountGT: Int - policyDismissedCountGTE: Int - policyDismissedCountLT: Int - policyDismissedCountLTE: Int - - """policy_pending_low_count field predicates""" - policyPendingLowCount: Int - policyPendingLowCountNEQ: Int - policyPendingLowCountIn: [Int!] - policyPendingLowCountNotIn: [Int!] - policyPendingLowCountGT: Int - policyPendingLowCountGTE: Int - policyPendingLowCountLT: Int - policyPendingLowCountLTE: Int - - """policy_pending_medium_count field predicates""" - policyPendingMediumCount: Int - policyPendingMediumCountNEQ: Int - policyPendingMediumCountIn: [Int!] - policyPendingMediumCountNotIn: [Int!] - policyPendingMediumCountGT: Int - policyPendingMediumCountGTE: Int - policyPendingMediumCountLT: Int - policyPendingMediumCountLTE: Int - - """policy_pending_high_count field predicates""" - policyPendingHighCount: Int - policyPendingHighCountNEQ: Int - policyPendingHighCountIn: [Int!] - policyPendingHighCountNotIn: [Int!] - policyPendingHighCountGT: Int - policyPendingHighCountGTE: Int - policyPendingHighCountLT: Int - policyPendingHighCountLTE: Int - - """policy_pending_critical_count field predicates""" - policyPendingCriticalCount: Int - policyPendingCriticalCountNEQ: Int - policyPendingCriticalCountIn: [Int!] - policyPendingCriticalCountNotIn: [Int!] - policyPendingCriticalCountGT: Int - policyPendingCriticalCountGTE: Int - policyPendingCriticalCountLT: Int - policyPendingCriticalCountLTE: Int - - """service_volume_per_hour field predicates""" - serviceVolumePerHour: Float - serviceVolumePerHourNEQ: Float - serviceVolumePerHourIn: [Float!] - serviceVolumePerHourNotIn: [Float!] - serviceVolumePerHourGT: Float - serviceVolumePerHourGTE: Float - serviceVolumePerHourLT: Float - serviceVolumePerHourLTE: Float - serviceVolumePerHourIsNil: Boolean - serviceVolumePerHourNotNil: Boolean - - """service_debug_volume_per_hour field predicates""" - serviceDebugVolumePerHour: Float - serviceDebugVolumePerHourNEQ: Float - serviceDebugVolumePerHourIn: [Float!] - serviceDebugVolumePerHourNotIn: [Float!] - serviceDebugVolumePerHourGT: Float - serviceDebugVolumePerHourGTE: Float - serviceDebugVolumePerHourLT: Float - serviceDebugVolumePerHourLTE: Float - serviceDebugVolumePerHourIsNil: Boolean - serviceDebugVolumePerHourNotNil: Boolean - - """service_info_volume_per_hour field predicates""" - serviceInfoVolumePerHour: Float - serviceInfoVolumePerHourNEQ: Float - serviceInfoVolumePerHourIn: [Float!] - serviceInfoVolumePerHourNotIn: [Float!] - serviceInfoVolumePerHourGT: Float - serviceInfoVolumePerHourGTE: Float - serviceInfoVolumePerHourLT: Float - serviceInfoVolumePerHourLTE: Float - serviceInfoVolumePerHourIsNil: Boolean - serviceInfoVolumePerHourNotNil: Boolean - - """service_warn_volume_per_hour field predicates""" - serviceWarnVolumePerHour: Float - serviceWarnVolumePerHourNEQ: Float - serviceWarnVolumePerHourIn: [Float!] - serviceWarnVolumePerHourNotIn: [Float!] - serviceWarnVolumePerHourGT: Float - serviceWarnVolumePerHourGTE: Float - serviceWarnVolumePerHourLT: Float - serviceWarnVolumePerHourLTE: Float - serviceWarnVolumePerHourIsNil: Boolean - serviceWarnVolumePerHourNotNil: Boolean - - """service_error_volume_per_hour field predicates""" - serviceErrorVolumePerHour: Float - serviceErrorVolumePerHourNEQ: Float - serviceErrorVolumePerHourIn: [Float!] - serviceErrorVolumePerHourNotIn: [Float!] - serviceErrorVolumePerHourGT: Float - serviceErrorVolumePerHourGTE: Float - serviceErrorVolumePerHourLT: Float - serviceErrorVolumePerHourLTE: Float - serviceErrorVolumePerHourIsNil: Boolean - serviceErrorVolumePerHourNotNil: Boolean - - """service_other_volume_per_hour field predicates""" - serviceOtherVolumePerHour: Float - serviceOtherVolumePerHourNEQ: Float - serviceOtherVolumePerHourIn: [Float!] - serviceOtherVolumePerHourNotIn: [Float!] - serviceOtherVolumePerHourGT: Float - serviceOtherVolumePerHourGTE: Float - serviceOtherVolumePerHourLT: Float - serviceOtherVolumePerHourLTE: Float - serviceOtherVolumePerHourIsNil: Boolean - serviceOtherVolumePerHourNotNil: Boolean - - """service_cost_per_hour_volume_usd field predicates""" - serviceCostPerHourVolumeUsd: Float - serviceCostPerHourVolumeUsdNEQ: Float - serviceCostPerHourVolumeUsdIn: [Float!] - serviceCostPerHourVolumeUsdNotIn: [Float!] - serviceCostPerHourVolumeUsdGT: Float - serviceCostPerHourVolumeUsdGTE: Float - serviceCostPerHourVolumeUsdLT: Float - serviceCostPerHourVolumeUsdLTE: Float - serviceCostPerHourVolumeUsdIsNil: Boolean - serviceCostPerHourVolumeUsdNotNil: Boolean - - """log_event_volume_per_hour field predicates""" - logEventVolumePerHour: Float - logEventVolumePerHourNEQ: Float - logEventVolumePerHourIn: [Float!] - logEventVolumePerHourNotIn: [Float!] - logEventVolumePerHourGT: Float - logEventVolumePerHourGTE: Float - logEventVolumePerHourLT: Float - logEventVolumePerHourLTE: Float - logEventVolumePerHourIsNil: Boolean - logEventVolumePerHourNotNil: Boolean - - """log_event_bytes_per_hour field predicates""" - logEventBytesPerHour: Float - logEventBytesPerHourNEQ: Float - logEventBytesPerHourIn: [Float!] - logEventBytesPerHourNotIn: [Float!] - logEventBytesPerHourGT: Float - logEventBytesPerHourGTE: Float - logEventBytesPerHourLT: Float - logEventBytesPerHourLTE: Float - logEventBytesPerHourIsNil: Boolean - logEventBytesPerHourNotNil: Boolean - - """log_event_cost_per_hour_bytes_usd field predicates""" - logEventCostPerHourBytesUsd: Float - logEventCostPerHourBytesUsdNEQ: Float - logEventCostPerHourBytesUsdIn: [Float!] - logEventCostPerHourBytesUsdNotIn: [Float!] - logEventCostPerHourBytesUsdGT: Float - logEventCostPerHourBytesUsdGTE: Float - logEventCostPerHourBytesUsdLT: Float - logEventCostPerHourBytesUsdLTE: Float - logEventCostPerHourBytesUsdIsNil: Boolean - logEventCostPerHourBytesUsdNotNil: Boolean - - """log_event_cost_per_hour_volume_usd field predicates""" - logEventCostPerHourVolumeUsd: Float - logEventCostPerHourVolumeUsdNEQ: Float - logEventCostPerHourVolumeUsdIn: [Float!] - logEventCostPerHourVolumeUsdNotIn: [Float!] - logEventCostPerHourVolumeUsdGT: Float - logEventCostPerHourVolumeUsdGTE: Float - logEventCostPerHourVolumeUsdLT: Float - logEventCostPerHourVolumeUsdLTE: Float - logEventCostPerHourVolumeUsdIsNil: Boolean - logEventCostPerHourVolumeUsdNotNil: Boolean - - """log_event_cost_per_hour_usd field predicates""" - logEventCostPerHourUsd: Float - logEventCostPerHourUsdNEQ: Float - logEventCostPerHourUsdIn: [Float!] - logEventCostPerHourUsdNotIn: [Float!] - logEventCostPerHourUsdGT: Float - logEventCostPerHourUsdGTE: Float - logEventCostPerHourUsdLT: Float - logEventCostPerHourUsdLTE: Float - logEventCostPerHourUsdIsNil: Boolean - logEventCostPerHourUsdNotNil: Boolean - - """estimated_volume_reduction_per_hour field predicates""" - estimatedVolumeReductionPerHour: Float - estimatedVolumeReductionPerHourNEQ: Float - estimatedVolumeReductionPerHourIn: [Float!] - estimatedVolumeReductionPerHourNotIn: [Float!] - estimatedVolumeReductionPerHourGT: Float - estimatedVolumeReductionPerHourGTE: Float - estimatedVolumeReductionPerHourLT: Float - estimatedVolumeReductionPerHourLTE: Float - estimatedVolumeReductionPerHourIsNil: Boolean - estimatedVolumeReductionPerHourNotNil: Boolean - - """estimated_bytes_reduction_per_hour field predicates""" - estimatedBytesReductionPerHour: Float - estimatedBytesReductionPerHourNEQ: Float - estimatedBytesReductionPerHourIn: [Float!] - estimatedBytesReductionPerHourNotIn: [Float!] - estimatedBytesReductionPerHourGT: Float - estimatedBytesReductionPerHourGTE: Float - estimatedBytesReductionPerHourLT: Float - estimatedBytesReductionPerHourLTE: Float - estimatedBytesReductionPerHourIsNil: Boolean - estimatedBytesReductionPerHourNotNil: Boolean - - """estimated_cost_reduction_per_hour_bytes_usd field predicates""" - estimatedCostReductionPerHourBytesUsd: Float - estimatedCostReductionPerHourBytesUsdNEQ: Float - estimatedCostReductionPerHourBytesUsdIn: [Float!] - estimatedCostReductionPerHourBytesUsdNotIn: [Float!] - estimatedCostReductionPerHourBytesUsdGT: Float - estimatedCostReductionPerHourBytesUsdGTE: Float - estimatedCostReductionPerHourBytesUsdLT: Float - estimatedCostReductionPerHourBytesUsdLTE: Float - estimatedCostReductionPerHourBytesUsdIsNil: Boolean - estimatedCostReductionPerHourBytesUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_volume_usd field predicates""" - estimatedCostReductionPerHourVolumeUsd: Float - estimatedCostReductionPerHourVolumeUsdNEQ: Float - estimatedCostReductionPerHourVolumeUsdIn: [Float!] - estimatedCostReductionPerHourVolumeUsdNotIn: [Float!] - estimatedCostReductionPerHourVolumeUsdGT: Float - estimatedCostReductionPerHourVolumeUsdGTE: Float - estimatedCostReductionPerHourVolumeUsdLT: Float - estimatedCostReductionPerHourVolumeUsdLTE: Float - estimatedCostReductionPerHourVolumeUsdIsNil: Boolean - estimatedCostReductionPerHourVolumeUsdNotNil: Boolean - - """estimated_cost_reduction_per_hour_usd field predicates""" - estimatedCostReductionPerHourUsd: Float - estimatedCostReductionPerHourUsdNEQ: Float - estimatedCostReductionPerHourUsdIn: [Float!] - estimatedCostReductionPerHourUsdNotIn: [Float!] - estimatedCostReductionPerHourUsdGT: Float - estimatedCostReductionPerHourUsdGTE: Float - estimatedCostReductionPerHourUsdLT: Float - estimatedCostReductionPerHourUsdLTE: Float - estimatedCostReductionPerHourUsdIsNil: Boolean - estimatedCostReductionPerHourUsdNotNil: Boolean - - """observed_volume_per_hour_before field predicates""" - observedVolumePerHourBefore: Float - observedVolumePerHourBeforeNEQ: Float - observedVolumePerHourBeforeIn: [Float!] - observedVolumePerHourBeforeNotIn: [Float!] - observedVolumePerHourBeforeGT: Float - observedVolumePerHourBeforeGTE: Float - observedVolumePerHourBeforeLT: Float - observedVolumePerHourBeforeLTE: Float - observedVolumePerHourBeforeIsNil: Boolean - observedVolumePerHourBeforeNotNil: Boolean - - """observed_volume_per_hour_after field predicates""" - observedVolumePerHourAfter: Float - observedVolumePerHourAfterNEQ: Float - observedVolumePerHourAfterIn: [Float!] - observedVolumePerHourAfterNotIn: [Float!] - observedVolumePerHourAfterGT: Float - observedVolumePerHourAfterGTE: Float - observedVolumePerHourAfterLT: Float - observedVolumePerHourAfterLTE: Float - observedVolumePerHourAfterIsNil: Boolean - observedVolumePerHourAfterNotNil: Boolean - - """observed_bytes_per_hour_before field predicates""" - observedBytesPerHourBefore: Float - observedBytesPerHourBeforeNEQ: Float - observedBytesPerHourBeforeIn: [Float!] - observedBytesPerHourBeforeNotIn: [Float!] - observedBytesPerHourBeforeGT: Float - observedBytesPerHourBeforeGTE: Float - observedBytesPerHourBeforeLT: Float - observedBytesPerHourBeforeLTE: Float - observedBytesPerHourBeforeIsNil: Boolean - observedBytesPerHourBeforeNotNil: Boolean - - """observed_bytes_per_hour_after field predicates""" - observedBytesPerHourAfter: Float - observedBytesPerHourAfterNEQ: Float - observedBytesPerHourAfterIn: [Float!] - observedBytesPerHourAfterNotIn: [Float!] - observedBytesPerHourAfterGT: Float - observedBytesPerHourAfterGTE: Float - observedBytesPerHourAfterLT: Float - observedBytesPerHourAfterLTE: Float - observedBytesPerHourAfterIsNil: Boolean - observedBytesPerHourAfterNotNil: Boolean - - """observed_cost_per_hour_before_bytes_usd field predicates""" - observedCostPerHourBeforeBytesUsd: Float - observedCostPerHourBeforeBytesUsdNEQ: Float - observedCostPerHourBeforeBytesUsdIn: [Float!] - observedCostPerHourBeforeBytesUsdNotIn: [Float!] - observedCostPerHourBeforeBytesUsdGT: Float - observedCostPerHourBeforeBytesUsdGTE: Float - observedCostPerHourBeforeBytesUsdLT: Float - observedCostPerHourBeforeBytesUsdLTE: Float - observedCostPerHourBeforeBytesUsdIsNil: Boolean - observedCostPerHourBeforeBytesUsdNotNil: Boolean - - """observed_cost_per_hour_before_volume_usd field predicates""" - observedCostPerHourBeforeVolumeUsd: Float - observedCostPerHourBeforeVolumeUsdNEQ: Float - observedCostPerHourBeforeVolumeUsdIn: [Float!] - observedCostPerHourBeforeVolumeUsdNotIn: [Float!] - observedCostPerHourBeforeVolumeUsdGT: Float - observedCostPerHourBeforeVolumeUsdGTE: Float - observedCostPerHourBeforeVolumeUsdLT: Float - observedCostPerHourBeforeVolumeUsdLTE: Float - observedCostPerHourBeforeVolumeUsdIsNil: Boolean - observedCostPerHourBeforeVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_before_usd field predicates""" - observedCostPerHourBeforeUsd: Float - observedCostPerHourBeforeUsdNEQ: Float - observedCostPerHourBeforeUsdIn: [Float!] - observedCostPerHourBeforeUsdNotIn: [Float!] - observedCostPerHourBeforeUsdGT: Float - observedCostPerHourBeforeUsdGTE: Float - observedCostPerHourBeforeUsdLT: Float - observedCostPerHourBeforeUsdLTE: Float - observedCostPerHourBeforeUsdIsNil: Boolean - observedCostPerHourBeforeUsdNotNil: Boolean - - """observed_cost_per_hour_after_bytes_usd field predicates""" - observedCostPerHourAfterBytesUsd: Float - observedCostPerHourAfterBytesUsdNEQ: Float - observedCostPerHourAfterBytesUsdIn: [Float!] - observedCostPerHourAfterBytesUsdNotIn: [Float!] - observedCostPerHourAfterBytesUsdGT: Float - observedCostPerHourAfterBytesUsdGTE: Float - observedCostPerHourAfterBytesUsdLT: Float - observedCostPerHourAfterBytesUsdLTE: Float - observedCostPerHourAfterBytesUsdIsNil: Boolean - observedCostPerHourAfterBytesUsdNotNil: Boolean - - """observed_cost_per_hour_after_volume_usd field predicates""" - observedCostPerHourAfterVolumeUsd: Float - observedCostPerHourAfterVolumeUsdNEQ: Float - observedCostPerHourAfterVolumeUsdIn: [Float!] - observedCostPerHourAfterVolumeUsdNotIn: [Float!] - observedCostPerHourAfterVolumeUsdGT: Float - observedCostPerHourAfterVolumeUsdGTE: Float - observedCostPerHourAfterVolumeUsdLT: Float - observedCostPerHourAfterVolumeUsdLTE: Float - observedCostPerHourAfterVolumeUsdIsNil: Boolean - observedCostPerHourAfterVolumeUsdNotNil: Boolean - - """observed_cost_per_hour_after_usd field predicates""" - observedCostPerHourAfterUsd: Float - observedCostPerHourAfterUsdNEQ: Float - observedCostPerHourAfterUsdIn: [Float!] - observedCostPerHourAfterUsdNotIn: [Float!] - observedCostPerHourAfterUsdGT: Float - observedCostPerHourAfterUsdGTE: Float - observedCostPerHourAfterUsdLT: Float - observedCostPerHourAfterUsdLTE: Float - observedCostPerHourAfterUsdIsNil: Boolean - observedCostPerHourAfterUsdNotNil: Boolean - - """refreshed_at field predicates""" - refreshedAt: Time - refreshedAtNEQ: Time - refreshedAtIn: [Time!] - refreshedAtNotIn: [Time!] - refreshedAtGT: Time - refreshedAtGTE: Time - refreshedAtLT: Time - refreshedAtLTE: Time + """ + service_id field predicates + """ + serviceID: ID + serviceIDNEQ: ID + serviceIDIn: [ID!] + serviceIDNotIn: [ID!] + """ + fact_group field predicates + """ + factGroup: String + factGroupNEQ: String + factGroupIn: [String!] + factGroupNotIn: [String!] + factGroupGT: String + factGroupGTE: String + factGroupLT: String + factGroupLTE: String + factGroupContains: String + factGroupHasPrefix: String + factGroupHasSuffix: String + factGroupEqualFold: String + factGroupContainsFold: String + """ + version field predicates + """ + version: Int + versionNEQ: Int + versionIn: [Int!] + versionNotIn: [Int!] + versionGT: Int + versionGTE: Int + versionLT: Int + versionLTE: Int + """ + namespace field predicates + """ + namespace: String + namespaceNEQ: String + namespaceIn: [String!] + namespaceNotIn: [String!] + namespaceGT: String + namespaceGTE: String + namespaceLT: String + namespaceLTE: String + namespaceContains: String + namespaceHasPrefix: String + namespaceHasSuffix: String + namespaceEqualFold: String + namespaceContainsFold: String + """ + fact_type field predicates + """ + factType: String + factTypeNEQ: String + factTypeIn: [String!] + factTypeNotIn: [String!] + factTypeGT: String + factTypeGTE: String + factTypeLT: String + factTypeLTE: String + factTypeContains: String + factTypeHasPrefix: String + factTypeHasSuffix: String + factTypeEqualFold: String + factTypeContainsFold: String + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + service edge predicates + """ + hasService: Boolean + hasServiceWith: [ServiceWhereInput!] +} +""" +Ordering options for Service connections +""" +input ServiceOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order Services. + """ + field: ServiceOrderField! +} +""" +Properties by which Service connections can be ordered. +""" +enum ServiceOrderField { + NAME + ENABLED + INITIAL_WEEKLY_LOG_COUNT + CREATED_AT + UPDATED_AT +} +type ServiceTeamMapping implements Node { + """ + Unique identifier of the service-team mapping + """ + id: ID! + """ + Denormalized account for RLS. Auto-set via trigger from service.account_id. + """ + accountID: UUID! + """ + Service assigned to a team + """ + serviceID: ID! + """ + Owning team for the service + """ + teamID: ID! + """ + When the mapping was created + """ + createdAt: Time! + """ + When the mapping was last updated + """ + updatedAt: Time! + """ + Service assigned to a team + """ + service: Service! + """ + Owning team for the service + """ + team: Team! +} +""" +A connection to a list of items. +""" +type ServiceTeamMappingConnection { + """ + A list of edges. + """ + edges: [ServiceTeamMappingEdge] + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} +""" +An edge in a connection. +""" +type ServiceTeamMappingEdge { + """ + The item at the end of the edge. + """ + node: ServiceTeamMapping + """ + A cursor for use in pagination. + """ + cursor: Cursor! +} +""" +Ordering options for ServiceTeamMapping connections +""" +input ServiceTeamMappingOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order ServiceTeamMappings. + """ + field: ServiceTeamMappingOrderField! +} +""" +Properties by which ServiceTeamMapping connections can be ordered. +""" +enum ServiceTeamMappingOrderField { + CREATED_AT + UPDATED_AT +} +""" +ServiceTeamMappingWhereInput is used for filtering ServiceTeamMapping objects. +Input was generated by ent. +""" +input ServiceTeamMappingWhereInput { + not: ServiceTeamMappingWhereInput + and: [ServiceTeamMappingWhereInput!] + or: [ServiceTeamMappingWhereInput!] + """ + id field predicates + """ + id: ID + idNEQ: ID + idIn: [ID!] + idNotIn: [ID!] + idGT: ID + idGTE: ID + idLT: ID + idLTE: ID + """ + account_id field predicates + """ + accountID: UUID + accountIDNEQ: UUID + accountIDIn: [UUID!] + accountIDNotIn: [UUID!] + accountIDGT: UUID + accountIDGTE: UUID + accountIDLT: UUID + accountIDLTE: UUID + """ + service_id field predicates + """ + serviceID: ID + serviceIDNEQ: ID + serviceIDIn: [ID!] + serviceIDNotIn: [ID!] + """ + team_id field predicates + """ + teamID: ID + teamIDNEQ: ID + teamIDIn: [ID!] + teamIDNotIn: [ID!] + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + service edge predicates + """ + hasService: Boolean + hasServiceWith: [ServiceWhereInput!] + """ + team edge predicates + """ + hasTeam: Boolean + hasTeamWith: [TeamWhereInput!] } - """ ServiceWhereInput is used for filtering Service objects. Input was generated by ent. @@ -5639,8 +4051,9 @@ input ServiceWhereInput { not: ServiceWhereInput and: [ServiceWhereInput!] or: [ServiceWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -5649,14 +4062,16 @@ input ServiceWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: ID accountIDNEQ: ID accountIDIn: [ID!] accountIDNotIn: [ID!] - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -5670,27 +4085,14 @@ input ServiceWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """description field predicates""" - description: String - descriptionNEQ: String - descriptionIn: [String!] - descriptionNotIn: [String!] - descriptionGT: String - descriptionGTE: String - descriptionLT: String - descriptionLTE: String - descriptionContains: String - descriptionHasPrefix: String - descriptionHasSuffix: String - descriptionEqualFold: String - descriptionContainsFold: String - - """enabled field predicates""" + """ + enabled field predicates + """ enabled: Boolean enabledNEQ: Boolean - - """initial_weekly_log_count field predicates""" + """ + initial_weekly_log_count field predicates + """ initialWeeklyLogCount: Int initialWeeklyLogCountNEQ: Int initialWeeklyLogCountIn: [Int!] @@ -5701,8 +4103,262 @@ input ServiceWhereInput { initialWeeklyLogCountLTE: Int initialWeeklyLogCountIsNil: Boolean initialWeeklyLogCountNotNil: Boolean - - """created_at field predicates""" + """ + created_at field predicates + """ + createdAt: Time + createdAtNEQ: Time + createdAtIn: [Time!] + createdAtNotIn: [Time!] + createdAtGT: Time + createdAtGTE: Time + createdAtLT: Time + createdAtLTE: Time + """ + updated_at field predicates + """ + updatedAt: Time + updatedAtNEQ: Time + updatedAtIn: [Time!] + updatedAtNotIn: [Time!] + updatedAtGT: Time + updatedAtGTE: Time + updatedAtLT: Time + updatedAtLTE: Time + """ + account edge predicates + """ + hasAccount: Boolean + hasAccountWith: [AccountWhereInput!] + """ + log_events edge predicates + """ + hasLogEvents: Boolean + hasLogEventsWith: [LogEventWhereInput!] + """ + facts edge predicates + """ + hasFacts: Boolean + hasFactsWith: [ServiceFactWhereInput!] + """ + findings edge predicates + """ + hasFindings: Boolean + hasFindingsWith: [FindingWhereInput!] + """ + team_mapping edge predicates + """ + hasTeamMapping: Boolean + hasTeamMappingWith: [ServiceTeamMappingWhereInput!] +} +type Team implements Node { + """ + Unique identifier of the team + """ + id: ID! + """ + Organization this team belongs to + """ + organizationID: ID! + """ + Human-readable team name within the organization + """ + name: String! + """ + Optional description of the team's scope and responsibilities + """ + description: String + """ + Optional external group identifier reserved for future SCIM mapping + """ + externalID: String + """ + When the team was created + """ + createdAt: Time! + """ + When the team was last updated + """ + updatedAt: Time! + """ + Organization this team belongs to + """ + organization: Organization! + """ + Memberships for this team + """ + memberships: [TeamMembership!] + """ + Service ownership mappings for this team + """ + serviceMappings: [ServiceTeamMapping!] +} +""" +A connection to a list of items. +""" +type TeamConnection { + """ + A list of edges. + """ + edges: [TeamEdge] + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} +""" +An edge in a connection. +""" +type TeamEdge { + """ + The item at the end of the edge. + """ + node: Team + """ + A cursor for use in pagination. + """ + cursor: Cursor! +} +type TeamMembership implements Node { + """ + Unique identifier of the team membership + """ + id: ID! + """ + Denormalized organization for RLS. Auto-set via trigger from team.organization_id. + """ + organizationID: ID! + """ + Team this membership belongs to + """ + teamID: ID! + """ + WorkOS user ID assigned to the team + """ + userID: String! + """ + When the membership was created + """ + createdAt: Time! + """ + When the membership was last updated + """ + updatedAt: Time! + """ + Team this membership belongs to + """ + team: Team! + """ + Organization this membership belongs to + """ + organization: Organization! +} +""" +A connection to a list of items. +""" +type TeamMembershipConnection { + """ + A list of edges. + """ + edges: [TeamMembershipEdge] + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} +""" +An edge in a connection. +""" +type TeamMembershipEdge { + """ + The item at the end of the edge. + """ + node: TeamMembership + """ + A cursor for use in pagination. + """ + cursor: Cursor! +} +""" +Ordering options for TeamMembership connections +""" +input TeamMembershipOrder { + """ + The ordering direction. + """ + direction: OrderDirection! = ASC + """ + The field by which to order TeamMemberships. + """ + field: TeamMembershipOrderField! +} +""" +Properties by which TeamMembership connections can be ordered. +""" +enum TeamMembershipOrderField { + CREATED_AT + UPDATED_AT +} +""" +TeamMembershipWhereInput is used for filtering TeamMembership objects. +Input was generated by ent. +""" +input TeamMembershipWhereInput { + not: TeamMembershipWhereInput + and: [TeamMembershipWhereInput!] + or: [TeamMembershipWhereInput!] + """ + id field predicates + """ + id: ID + idNEQ: ID + idIn: [ID!] + idNotIn: [ID!] + idGT: ID + idGTE: ID + idLT: ID + idLTE: ID + """ + organization_id field predicates + """ + organizationID: ID + organizationIDNEQ: ID + organizationIDIn: [ID!] + organizationIDNotIn: [ID!] + """ + team_id field predicates + """ + teamID: ID + teamIDNEQ: ID + teamIDIn: [ID!] + teamIDNotIn: [ID!] + """ + user_id field predicates + """ + userID: String + userIDNEQ: String + userIDIn: [String!] + userIDNotIn: [String!] + userIDGT: String + userIDGTE: String + userIDLT: String + userIDLTE: String + userIDContains: String + userIDHasPrefix: String + userIDHasSuffix: String + userIDEqualFold: String + userIDContainsFold: String + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -5711,8 +4367,9 @@ input ServiceWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -5721,78 +4378,39 @@ input ServiceWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """account edge predicates""" - hasAccount: Boolean - hasAccountWith: [AccountWhereInput!] - - """log_events edge predicates""" - hasLogEvents: Boolean - hasLogEventsWith: [LogEventWhereInput!] -} - -type Team implements Node { - """Unique identifier of the team""" - id: ID! - """ - Denormalized for tenant isolation. Auto-set via trigger from workspace.account_id. + team edge predicates """ - accountID: UUID! - - """Parent workspace this team belongs to""" - workspaceID: ID! - - """Human-readable name within the workspace""" - name: String! - - """When the team was created""" - createdAt: Time! - - """When the team was last updated""" - updatedAt: Time! - - """Workspace this team belongs to""" - workspace: Workspace! -} - -"""A connection to a list of items.""" -type TeamConnection { - """A list of edges.""" - edges: [TeamEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" - totalCount: Int! -} - -"""An edge in a connection.""" -type TeamEdge { - """The item at the end of the edge.""" - node: Team - - """A cursor for use in pagination.""" - cursor: Cursor! + hasTeam: Boolean + hasTeamWith: [TeamWhereInput!] + """ + organization edge predicates + """ + hasOrganization: Boolean + hasOrganizationWith: [OrganizationWhereInput!] } - -"""Ordering options for Team connections""" +""" +Ordering options for Team connections +""" input TeamOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order Teams.""" + """ + The field by which to order Teams. + """ field: TeamOrderField! } - -"""Properties by which Team connections can be ordered.""" +""" +Properties by which Team connections can be ordered. +""" enum TeamOrderField { NAME + DESCRIPTION CREATED_AT UPDATED_AT } - """ TeamWhereInput is used for filtering Team objects. Input was generated by ent. @@ -5801,8 +4419,9 @@ input TeamWhereInput { not: TeamWhereInput and: [TeamWhereInput!] or: [TeamWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -5811,24 +4430,16 @@ input TeamWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """workspace_id field predicates""" - workspaceID: ID - workspaceIDNEQ: ID - workspaceIDIn: [ID!] - workspaceIDNotIn: [ID!] - - """name field predicates""" + """ + organization_id field predicates + """ + organizationID: ID + organizationIDNEQ: ID + organizationIDIn: [ID!] + organizationIDNotIn: [ID!] + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -5842,8 +4453,45 @@ input TeamWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """created_at field predicates""" + """ + description field predicates + """ + description: String + descriptionNEQ: String + descriptionIn: [String!] + descriptionNotIn: [String!] + descriptionGT: String + descriptionGTE: String + descriptionLT: String + descriptionLTE: String + descriptionContains: String + descriptionHasPrefix: String + descriptionHasSuffix: String + descriptionIsNil: Boolean + descriptionNotNil: Boolean + descriptionEqualFold: String + descriptionContainsFold: String + """ + external_id field predicates + """ + externalID: String + externalIDNEQ: String + externalIDIn: [String!] + externalIDNotIn: [String!] + externalIDGT: String + externalIDGTE: String + externalIDLT: String + externalIDLTE: String + externalIDContains: String + externalIDHasPrefix: String + externalIDHasSuffix: String + externalIDIsNil: Boolean + externalIDNotNil: Boolean + externalIDEqualFold: String + externalIDContainsFold: String + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -5852,8 +4500,9 @@ input TeamWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -5862,97 +4511,35 @@ input TeamWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """workspace edge predicates""" - hasWorkspace: Boolean - hasWorkspaceWith: [WorkspaceWhereInput!] -} - -"""Text content from the user or assistant.""" -type TextBlock { - content: String! -} - -"""Text content from the user or assistant.""" -input TextBlockInput { - content: String! -} - -"""The AI's internal reasoning (extended thinking).""" -type ThinkingBlock { - content: String! -} - -"""The AI's internal reasoning (extended thinking).""" -input ThinkingBlockInput { - content: String! + """ + organization edge predicates + """ + hasOrganization: Boolean + hasOrganizationWith: [OrganizationWhereInput!] + """ + memberships edge predicates + """ + hasMemberships: Boolean + hasMembershipsWith: [TeamMembershipWhereInput!] + """ + service_mappings edge predicates + """ + hasServiceMappings: Boolean + hasServiceMappingsWith: [ServiceTeamMappingWhereInput!] } - -"""The builtin Time type""" +""" +The builtin Time type +""" scalar Time - -"""The result of a tool call.""" -type ToolResult { - """The ID of the tool call this result is for.""" - toolUseId: String! - - """Whether the tool execution resulted in an error.""" - isError: Boolean - - """Human-readable error message when isError is true.""" - error: String - - """Structured result data (JSON object).""" - content: Map -} - -"""The result of a tool call.""" -input ToolResultInput { - """The ID of the tool call this result is for.""" - toolUseId: String! - - """Whether the tool execution resulted in an error.""" - isError: Boolean! - - """Human-readable error message when isError is true.""" - error: String - - """Structured result data (JSON object).""" - content: Map -} - -"""A tool call from the assistant.""" -type ToolUse { - """Unique identifier for this tool call.""" - id: String! - - """The name of the tool being called.""" - name: String! - - """The input parameters for the tool (JSON object).""" - input: Map! -} - -"""A tool call from the assistant.""" -input ToolUseInput { - """Unique identifier for this tool call.""" - id: String! - - """The name of the tool being called.""" - name: String! - - """The input parameters for the tool (JSON object).""" - input: Map! -} - """ UpdateAccountInput is used for update Account object. Input was generated by ent. """ input UpdateAccountInput { - """Human-readable name within the organization""" + """ + Human-readable name within the organization + """ name: String - """ Multiplier applied to volume data via trigger. 1 = real data, >1 = scaled for demos. """ @@ -5961,550 +4548,193 @@ input UpdateAccountInput { datadogAccountID: ID clearDatadogAccount: Boolean } - """ UpdateConversationInput is used for update Conversation object. Input was generated by ent. """ input UpdateConversationInput { - """AI-generated title, set after first exchange""" + """ + AI-generated title, set after first exchange + """ title: String clearTitle: Boolean - viewID: ID - clearView: Boolean } - """ UpdateDatadogAccountInput is used for update DatadogAccount object. Input was generated by ent. """ input UpdateDatadogAccountInput { - """Display name for this Datadog account""" + """ + Display name for this Datadog account + """ name: String - """ - Datadog regional site. US1: datadoghq.com, US3: us3.datadoghq.com, US5: - us5.datadoghq.com, EU1: datadoghq.eu, US1_FED: ddog-gov.com, AP1: - ap1.datadoghq.com, AP2: ap2.datadoghq.com. + Datadog regional site. Values: US1 = datadoghq.com.; US3 = us3.datadoghq.com.; US5 = us5.datadoghq.com.; EU1 = datadoghq.eu.; US1_FED = ddog-gov.com.; AP1 = ap1.datadoghq.com.; AP2 = ap2.datadoghq.com. """ site: DatadogAccountSite - """ - Cost per GB of log data ingested (USD). NULL = using Datadog's published rate - ($0.10/GB). Set to override with actual contract rate. + Cost per GB of log data ingested (USD). NULL = using Datadog's published rate ($0.10/GB). Set to override with actual contract rate. """ costPerGBIngested: Float clearCostPerGBIngested: Boolean - - """ - Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = - 80%). Leaves headroom for the customer's own API usage. - """ - rateLimitUtilization: Float - clearRateLimitUtilization: Boolean - accountID: ID -} - -""" -UpdateEdgeApiKeyInput is used for update EdgeApiKey object. -Input was generated by ent. -""" -input UpdateEdgeApiKeyInput { - """User-provided name for this key (e.g., 'Production Collector')""" - name: String - - """When this key was revoked (null if active)""" - revokedAt: Time - clearRevokedAt: Boolean -} - -""" -UpdateOrganizationInput is used for update Organization object. -Input was generated by ent. -""" -input UpdateOrganizationInput { - """Human-readable name, unique across the system""" - name: String -} - -""" -UpdateServiceInput is used for update Service object. -Input was generated by ent. -""" -input UpdateServiceInput { - """Whether log analysis and policy generation is active for this service""" - enabled: Boolean -} - -""" -UpdateTeamInput is used for update Team object. -Input was generated by ent. -""" -input UpdateTeamInput { - """Human-readable name within the workspace""" - name: String - workspaceID: ID -} - -""" -UpdateWorkspaceInput is used for update Workspace object. -Input was generated by ent. -""" -input UpdateWorkspaceInput { - """Human-readable name within the account""" - name: String - - """ - Primary purpose determining evaluation strategy. observability: performance - and reliability, security: threat detection, compliance: regulatory requirements. - """ - purpose: WorkspacePurpose - accountID: ID -} - -scalar UUID - -input ValidateDatadogApiKeyInput { - apiKey: String! - site: DatadogAccountSite! -} - -type ValidateDatadogApiKeyResult { - valid: Boolean! - error: String -} - -type View implements Node { - """Unique identifier""" - id: ID! - - """ - Denormalized for tenant isolation. Auto-set via trigger from message.account_id. - """ - accountID: UUID! - - """Assistant message that created this view via show_view tool call""" - messageID: ID! - - """Denormalized from message for easier queries""" - conversationID: ID! - - """Parent view if this is a refinement/iteration""" - forkedFromID: ID - - """ - Which catalog entity this view queries. service: applications, log_event: event patterns, policy: quality recommendations. - """ - entityType: ViewEntityType! - - """Raw SQL query executed against the client's local SQLite database""" - query: String! - - """WorkOS user ID who triggered this view creation""" - createdBy: String! - - """When the view was created""" - createdAt: Time! - - """Assistant message that created this view""" - message: Message! - - """Conversation this view was created in""" - conversation: Conversation! - - """Parent view if this is a fork""" - forkedFrom: View - forks: [View!] - - """Users who favorited this view""" - favorites: [ViewFavorite!] - - """Conversations created to iterate on this view""" - iterationConversations: [Conversation!] -} - -"""A connection to a list of items.""" -type ViewConnection { - """A list of edges.""" - edges: [ViewEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" - totalCount: Int! -} - -"""An edge in a connection.""" -type ViewEdge { - """The item at the end of the edge.""" - node: View - - """A cursor for use in pagination.""" - cursor: Cursor! -} - -"""ViewEntityType is enum for the field entity_type""" -enum ViewEntityType { - service - log_event - policy + """ + Fraction of the API rate limit to consume (0.0-1.0). NULL = default (0.8 = 80%). Leaves headroom for the customer's own API usage. + """ + rateLimitUtilization: Float + clearRateLimitUtilization: Boolean + accountID: ID } - """ -The authenticated user making the request. -Returns null for organizationId if the user is not assigned to an organization. +UpdateEdgeApiKeyInput is used for update EdgeApiKey object. +Input was generated by ent. """ -type Viewer { - """The user's unique identifier (WorkOS user ID).""" - id: String! - - """The user's email address.""" - email: String! - +input UpdateEdgeApiKeyInput { """ - The organization ID the user is authenticated for, or null if not assigned to any organization. + User-provided name for this key (e.g., 'Production Collector') """ - organizationId: UUID -} - -type ViewFavorite implements Node { - """Unique identifier""" - id: ID! - + name: String """ - Denormalized for tenant isolation. Auto-set via trigger from view.account_id. + When this key was revoked (null if active) """ - accountID: UUID! - - """The view being favorited""" - viewID: ID! - - """WorkOS user ID who favorited this view""" - userID: String! - - """When the view was favorited""" - createdAt: Time! - - """The view being favorited""" - view: View! -} - -"""A connection to a list of items.""" -type ViewFavoriteConnection { - """A list of edges.""" - edges: [ViewFavoriteEdge] - - """Information to aid in pagination.""" - pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" - totalCount: Int! -} - -"""An edge in a connection.""" -type ViewFavoriteEdge { - """The item at the end of the edge.""" - node: ViewFavorite - - """A cursor for use in pagination.""" - cursor: Cursor! -} - -"""Ordering options for ViewFavorite connections""" -input ViewFavoriteOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order ViewFavorites.""" - field: ViewFavoriteOrderField! -} - -"""Properties by which ViewFavorite connections can be ordered.""" -enum ViewFavoriteOrderField { - CREATED_AT + revokedAt: Time + clearRevokedAt: Boolean } - """ -ViewFavoriteWhereInput is used for filtering ViewFavorite objects. +UpdateOrganizationInput is used for update Organization object. Input was generated by ent. """ -input ViewFavoriteWhereInput { - not: ViewFavoriteWhereInput - and: [ViewFavoriteWhereInput!] - or: [ViewFavoriteWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """view_id field predicates""" - viewID: ID - viewIDNEQ: ID - viewIDIn: [ID!] - viewIDNotIn: [ID!] - - """user_id field predicates""" - userID: String - userIDNEQ: String - userIDIn: [String!] - userIDNotIn: [String!] - userIDGT: String - userIDGTE: String - userIDLT: String - userIDLTE: String - userIDContains: String - userIDHasPrefix: String - userIDHasSuffix: String - userIDEqualFold: String - userIDContainsFold: String - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """view edge predicates""" - hasView: Boolean - hasViewWith: [ViewWhereInput!] -} - -"""Ordering options for View connections""" -input ViewOrder { - """The ordering direction.""" - direction: OrderDirection! = ASC - - """The field by which to order Views.""" - field: ViewOrderField! +input UpdateOrganizationInput { + """ + Human-readable name, unique across the system + """ + name: String } - -"""Properties by which View connections can be ordered.""" -enum ViewOrderField { - ENTITY_TYPE - CREATED_AT +""" +UpdateTeamInput is used for update Team object. +Input was generated by ent. +""" +input UpdateTeamInput { + """ + Human-readable team name within the organization + """ + name: String + """ + Optional description of the team's scope and responsibilities + """ + description: String + clearDescription: Boolean + """ + Optional external group identifier reserved for future SCIM mapping + """ + externalID: String + clearExternalID: Boolean + organizationID: ID } - """ -ViewWhereInput is used for filtering View objects. +UpdateWorkspaceInput is used for update Workspace object. Input was generated by ent. """ -input ViewWhereInput { - not: ViewWhereInput - and: [ViewWhereInput!] - or: [ViewWhereInput!] - - """id field predicates""" - id: ID - idNEQ: ID - idIn: [ID!] - idNotIn: [ID!] - idGT: ID - idGTE: ID - idLT: ID - idLTE: ID - - """account_id field predicates""" - accountID: UUID - accountIDNEQ: UUID - accountIDIn: [UUID!] - accountIDNotIn: [UUID!] - accountIDGT: UUID - accountIDGTE: UUID - accountIDLT: UUID - accountIDLTE: UUID - - """message_id field predicates""" - messageID: ID - messageIDNEQ: ID - messageIDIn: [ID!] - messageIDNotIn: [ID!] - - """conversation_id field predicates""" - conversationID: ID - conversationIDNEQ: ID - conversationIDIn: [ID!] - conversationIDNotIn: [ID!] - - """forked_from_id field predicates""" - forkedFromID: ID - forkedFromIDNEQ: ID - forkedFromIDIn: [ID!] - forkedFromIDNotIn: [ID!] - forkedFromIDIsNil: Boolean - forkedFromIDNotNil: Boolean - - """entity_type field predicates""" - entityType: ViewEntityType - entityTypeNEQ: ViewEntityType - entityTypeIn: [ViewEntityType!] - entityTypeNotIn: [ViewEntityType!] - - """query field predicates""" - query: String - queryNEQ: String - queryIn: [String!] - queryNotIn: [String!] - queryGT: String - queryGTE: String - queryLT: String - queryLTE: String - queryContains: String - queryHasPrefix: String - queryHasSuffix: String - queryEqualFold: String - queryContainsFold: String - - """created_by field predicates""" - createdBy: String - createdByNEQ: String - createdByIn: [String!] - createdByNotIn: [String!] - createdByGT: String - createdByGTE: String - createdByLT: String - createdByLTE: String - createdByContains: String - createdByHasPrefix: String - createdByHasSuffix: String - createdByEqualFold: String - createdByContainsFold: String - - """created_at field predicates""" - createdAt: Time - createdAtNEQ: Time - createdAtIn: [Time!] - createdAtNotIn: [Time!] - createdAtGT: Time - createdAtGTE: Time - createdAtLT: Time - createdAtLTE: Time - - """message edge predicates""" - hasMessage: Boolean - hasMessageWith: [MessageWhereInput!] - - """conversation edge predicates""" - hasConversation: Boolean - hasConversationWith: [ConversationWhereInput!] - - """forked_from edge predicates""" - hasForkedFrom: Boolean - hasForkedFromWith: [ViewWhereInput!] - - """forks edge predicates""" - hasForks: Boolean - hasForksWith: [ViewWhereInput!] - - """favorites edge predicates""" - hasFavorites: Boolean - hasFavoritesWith: [ViewFavoriteWhereInput!] - - """iteration_conversations edge predicates""" - hasIterationConversations: Boolean - hasIterationConversationsWith: [ConversationWhereInput!] +input UpdateWorkspaceInput { + """ + Human-readable name within the account + """ + name: String + """ + Primary purpose determining evaluation strategy. Values: observability = Performance, reliability, and operational visibility.; security = Threat detection, investigation, and security posture.; compliance = Regulatory, privacy, and policy compliance review. + """ + purpose: WorkspacePurpose + accountID: ID } - type Workspace implements Node { - """Unique identifier of the workspace""" + """ + Unique identifier of the workspace + """ id: ID! - - """Parent account this workspace belongs to""" + """ + Parent account this workspace belongs to + """ accountID: ID! - - """Human-readable name within the account""" + """ + Human-readable name within the account + """ name: String! - """ - Primary purpose determining evaluation strategy. observability: performance - and reliability, security: threat detection, compliance: regulatory requirements. + Primary purpose determining evaluation strategy. Values: observability = Performance, reliability, and operational visibility.; security = Threat detection, investigation, and security posture.; compliance = Regulatory, privacy, and policy compliance review. """ purpose: WorkspacePurpose! - - """When the workspace was created""" + """ + When the workspace was created + """ createdAt: Time! - - """When the workspace was last updated""" + """ + When the workspace was last updated + """ updatedAt: Time! - - """Account this workspace belongs to""" + """ + Account this workspace belongs to + """ account: Account! - - """Teams assigned to manage this workspace""" - teams: [Team!] - - """Chat conversations in this workspace""" - conversations: [Conversation!] - - """Log event policies for this workspace""" - logEventPolicies: [LogEventPolicy!] } - -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type WorkspaceConnection { - """A list of edges.""" + """ + A list of edges. + """ edges: [WorkspaceEdge] - - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - - """Identifies the total count of items in the connection.""" + """ + Identifies the total count of items in the connection. + """ totalCount: Int! } - -"""An edge in a connection.""" +""" +An edge in a connection. +""" type WorkspaceEdge { - """The item at the end of the edge.""" + """ + The item at the end of the edge. + """ node: Workspace - - """A cursor for use in pagination.""" + """ + A cursor for use in pagination. + """ cursor: Cursor! } - -"""Ordering options for Workspace connections""" +""" +Ordering options for Workspace connections +""" input WorkspaceOrder { - """The ordering direction.""" + """ + The ordering direction. + """ direction: OrderDirection! = ASC - - """The field by which to order Workspaces.""" + """ + The field by which to order Workspaces. + """ field: WorkspaceOrderField! } - -"""Properties by which Workspace connections can be ordered.""" +""" +Properties by which Workspace connections can be ordered. +""" enum WorkspaceOrderField { NAME PURPOSE CREATED_AT UPDATED_AT } - -"""WorkspacePurpose is enum for the field purpose""" -enum WorkspacePurpose { +""" +WorkspacePurpose is enum for the field purpose +""" +enum WorkspacePurpose @goModel(model: "github.com/usetero/control-plane/internal/infra/postgres/workspace.Purpose") { observability security compliance } - """ WorkspaceWhereInput is used for filtering Workspace objects. Input was generated by ent. @@ -6513,8 +4743,9 @@ input WorkspaceWhereInput { not: WorkspaceWhereInput and: [WorkspaceWhereInput!] or: [WorkspaceWhereInput!] - - """id field predicates""" + """ + id field predicates + """ id: ID idNEQ: ID idIn: [ID!] @@ -6523,14 +4754,16 @@ input WorkspaceWhereInput { idGTE: ID idLT: ID idLTE: ID - - """account_id field predicates""" + """ + account_id field predicates + """ accountID: ID accountIDNEQ: ID accountIDIn: [ID!] accountIDNotIn: [ID!] - - """name field predicates""" + """ + name field predicates + """ name: String nameNEQ: String nameIn: [String!] @@ -6544,14 +4777,16 @@ input WorkspaceWhereInput { nameHasSuffix: String nameEqualFold: String nameContainsFold: String - - """purpose field predicates""" + """ + purpose field predicates + """ purpose: WorkspacePurpose purposeNEQ: WorkspacePurpose purposeIn: [WorkspacePurpose!] purposeNotIn: [WorkspacePurpose!] - - """created_at field predicates""" + """ + created_at field predicates + """ createdAt: Time createdAtNEQ: Time createdAtIn: [Time!] @@ -6560,8 +4795,9 @@ input WorkspaceWhereInput { createdAtGTE: Time createdAtLT: Time createdAtLTE: Time - - """updated_at field predicates""" + """ + updated_at field predicates + """ updatedAt: Time updatedAtNEQ: Time updatedAtIn: [Time!] @@ -6570,21 +4806,9 @@ input WorkspaceWhereInput { updatedAtGTE: Time updatedAtLT: Time updatedAtLTE: Time - - """account edge predicates""" + """ + account edge predicates + """ hasAccount: Boolean hasAccountWith: [AccountWhereInput!] - - """teams edge predicates""" - hasTeams: Boolean - hasTeamsWith: [TeamWhereInput!] - - """conversations edge predicates""" - hasConversations: Boolean - hasConversationsWith: [ConversationWhereInput!] - - """log_event_policies edge predicates""" - hasLogEventPolicies: Boolean - hasLogEventPoliciesWith: [LogEventPolicyWhereInput!] } - diff --git a/internal/boundary/powersync/indexes.go b/internal/boundary/powersync/indexes.go index d8e866e..7be79a0 100644 --- a/internal/boundary/powersync/indexes.go +++ b/internal/boundary/powersync/indexes.go @@ -68,9 +68,9 @@ var clientIndexes = map[string][]SchemaIndex{ }, } -// applyClientIndexes merges client-side indexes into fetched schema tables. +// ApplyClientIndexes merges client-side indexes into schema tables. // Tables without indexes get an empty slice so JSON encodes as [] not null. -func applyClientIndexes(tables []SchemaTable) []SchemaTable { +func ApplyClientIndexes(tables []SchemaTable) []SchemaTable { for i, table := range tables { if indexes, ok := clientIndexes[table.Name]; ok { tables[i].Indexes = indexes @@ -80,3 +80,7 @@ func applyClientIndexes(tables []SchemaTable) []SchemaTable { } return tables } + +func applyClientIndexes(tables []SchemaTable) []SchemaTable { + return ApplyClientIndexes(tables) +} diff --git a/internal/core/bootstrap/completion.go b/internal/core/bootstrap/completion.go index 3498fd1..861e59e 100644 --- a/internal/core/bootstrap/completion.go +++ b/internal/core/bootstrap/completion.go @@ -7,22 +7,20 @@ import ( // Completion is the required onboarding payload for entering chat. type Completion struct { - User *auth.User - Org domain.Organization - Account domain.Account - Workspace domain.Workspace + User *auth.User + Org domain.Organization + Account domain.Account } // CompleteOnboarding validates bootstrap state and returns completion payload. func CompleteOnboarding(state State) (Completion, bool) { - if state.User == nil || state.Org == nil || state.Account == nil || state.Workspace == nil { + if state.User == nil || state.Org == nil || state.Account == nil { return Completion{}, false } return Completion{ - User: state.User, - Org: *state.Org, - Account: *state.Account, - Workspace: *state.Workspace, + User: state.User, + Org: *state.Org, + Account: *state.Account, }, true } diff --git a/internal/core/bootstrap/completion_test.go b/internal/core/bootstrap/completion_test.go index 806b8f5..49b6e85 100644 --- a/internal/core/bootstrap/completion_test.go +++ b/internal/core/bootstrap/completion_test.go @@ -13,13 +13,11 @@ func TestCompleteOnboarding(t *testing.T) { user := &auth.User{ID: "user-1"} org := &domain.Organization{ID: "org-1", Name: "Org 1"} account := &domain.Account{ID: "acc-1", Name: "Account 1"} - workspace := &domain.Workspace{ID: "ws-1", Name: "Workspace 1"} got, ok := CompleteOnboarding(State{ - User: user, - Org: org, - Account: account, - Workspace: workspace, + User: user, + Org: org, + Account: account, }) if !ok { t.Fatal("expected completion payload") @@ -33,29 +31,24 @@ func TestCompleteOnboarding(t *testing.T) { if got.Account.ID != "acc-1" { t.Fatalf("account = %#v, want acc-1", got.Account) } - if got.Workspace.ID != "ws-1" { - t.Fatalf("workspace = %#v, want ws-1", got.Workspace) - } } func TestCompleteOnboardingMissingRequirements(t *testing.T) { t.Parallel() base := State{ - User: &auth.User{ID: "user-1"}, - Org: &domain.Organization{ID: "org-1"}, - Account: &domain.Account{ID: "acc-1"}, - Workspace: &domain.Workspace{ID: "ws-1"}, + User: &auth.User{ID: "user-1"}, + Org: &domain.Organization{ID: "org-1"}, + Account: &domain.Account{ID: "acc-1"}, } cases := []struct { name string state State }{ - {name: "missing user", state: State{Org: base.Org, Account: base.Account, Workspace: base.Workspace}}, - {name: "missing org", state: State{User: base.User, Account: base.Account, Workspace: base.Workspace}}, - {name: "missing account", state: State{User: base.User, Org: base.Org, Workspace: base.Workspace}}, - {name: "missing workspace", state: State{User: base.User, Org: base.Org, Account: base.Account}}, + {name: "missing user", state: State{Org: base.Org, Account: base.Account}}, + {name: "missing org", state: State{User: base.User, Account: base.Account}}, + {name: "missing account", state: State{User: base.User, Org: base.Org}}, } for _, tt := range cases { diff --git a/internal/core/bootstrap/events_test.go b/internal/core/bootstrap/events_test.go index 9cdcf2e..6153d74 100644 --- a/internal/core/bootstrap/events_test.go +++ b/internal/core/bootstrap/events_test.go @@ -29,10 +29,9 @@ func TestApplyEventSyncComplete(t *testing.T) { t.Parallel() state := State{ - User: &auth.User{ID: "user-1"}, - Org: &domain.Organization{ID: "org-1"}, - Account: &domain.Account{ID: "acc-1"}, - Workspace: &domain.Workspace{ID: "ws-1"}, + User: &auth.User{ID: "user-1"}, + Org: &domain.Organization{ID: "org-1"}, + Account: &domain.Account{ID: "acc-1"}, } got := ApplyEvent(state, Event{Kind: EventSyncComplete}) if got.Kind != TransitionComplete { @@ -41,7 +40,7 @@ func TestApplyEventSyncComplete(t *testing.T) { if got.Completion.User == nil || got.Completion.User.ID != "user-1" { t.Fatalf("completion user = %#v", got.Completion.User) } - if got.Completion.Org.ID != "org-1" || got.Completion.Account.ID != "acc-1" || got.Completion.Workspace.ID != "ws-1" { + if got.Completion.Org.ID != "org-1" || got.Completion.Account.ID != "acc-1" { t.Fatalf("unexpected completion payload: %#v", got.Completion) } } diff --git a/internal/core/bootstrap/gate_requirements.go b/internal/core/bootstrap/gate_requirements.go index c66e554..2e5c0e6 100644 --- a/internal/core/bootstrap/gate_requirements.go +++ b/internal/core/bootstrap/gate_requirements.go @@ -14,7 +14,7 @@ func RequirementForGate(gate Gate) GateRequirement { case GateDatadogDiscovery: return GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsDDAccount: true} case GateSync: - return GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsWorkspace: true} + return GateRequirement{NeedsOrg: true, NeedsAccount: true} default: return GateRequirement{} } diff --git a/internal/core/bootstrap/gate_requirements_test.go b/internal/core/bootstrap/gate_requirements_test.go index 2cea9d0..a294794 100644 --- a/internal/core/bootstrap/gate_requirements_test.go +++ b/internal/core/bootstrap/gate_requirements_test.go @@ -15,7 +15,7 @@ func TestRequirementForGate(t *testing.T) { {gate: GateDatadogAPIKey, want: GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsDDSite: true}}, {gate: GateDatadogAppKey, want: GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsDDSite: true, NeedsDDAPIKey: true}}, {gate: GateDatadogDiscovery, want: GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsDDAccount: true}}, - {gate: GateSync, want: GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsWorkspace: true}}, + {gate: GateSync, want: GateRequirement{NeedsOrg: true, NeedsAccount: true}}, } for _, tc := range cases { diff --git a/internal/core/bootstrap/messages.go b/internal/core/bootstrap/messages.go index 4248666..3ef58ac 100644 --- a/internal/core/bootstrap/messages.go +++ b/internal/core/bootstrap/messages.go @@ -77,15 +77,15 @@ type WorkspaceSelected struct { type SyncComplete struct{} type OnboardingComplete struct { - User *auth.User - Org domain.Organization - Account domain.Account - Workspace domain.Workspace + User *auth.User + Org domain.Organization + Account domain.Account } type PreflightState struct { Outcome PreflightOutcome HasValidAuth bool + User *auth.User Role string ActiveOrgID domain.OrganizationID DefaultAccountID domain.AccountID diff --git a/internal/core/bootstrap/requirements_test.go b/internal/core/bootstrap/requirements_test.go index d22a85b..3878b6e 100644 --- a/internal/core/bootstrap/requirements_test.go +++ b/internal/core/bootstrap/requirements_test.go @@ -38,11 +38,11 @@ func TestRewindGate(t *testing.T) { want: GateDatadogAPIKey, }, { - name: "sync requirement rewinds to workspace when workspace missing", + name: "sync requirement stays on sync once org and account are present", target: GateSync, - req: GateRequirement{NeedsOrg: true, NeedsAccount: true, NeedsWorkspace: true}, + req: GateRequirement{NeedsOrg: true, NeedsAccount: true}, state: State{Org: &domain.Organization{ID: "org-1"}, Account: &domain.Account{ID: "acc-1"}}, - want: GateWorkspaceSelect, + want: GateSync, }, } diff --git a/internal/core/bootstrap/state.go b/internal/core/bootstrap/state.go index f81e142..cd6ec75 100644 --- a/internal/core/bootstrap/state.go +++ b/internal/core/bootstrap/state.go @@ -32,6 +32,9 @@ func ApplyPreflight(state State, resolved PreflightState) (State, Gate) { if resolved.Account != nil { state.Account = resolved.Account } + if resolved.User != nil { + state.User = resolved.User + } return state, next } @@ -90,7 +93,7 @@ func ApplyRuntimeReady(state State, org domain.Organization, account domain.Acco } func ApplyDatadogReady(state State) (State, Gate) { - return state, GateWorkspaceSelect + return state, GateSync } func ApplyDatadogNeeded(state State) (State, Gate) { @@ -113,7 +116,7 @@ func ApplyDatadogAccountCreated(state State, datadogAccountID domain.DatadogAcco } func ApplyDatadogDiscoveryComplete(state State) (State, Gate) { - return state, GateWorkspaceSelect + return state, GateSync } func ApplyWorkspaceSelected(state State, workspace domain.Workspace) (State, Gate) { diff --git a/internal/domain/conversation.go b/internal/domain/conversation.go index e6e8613..a42c3da 100644 --- a/internal/domain/conversation.go +++ b/internal/domain/conversation.go @@ -21,11 +21,10 @@ func (id ConversationID) String() string { // Conversation represents a chat conversation. type Conversation struct { - ID ConversationID `json:"id"` - WorkspaceID WorkspaceID `json:"workspace_id"` - Title string `json:"title"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID ConversationID `json:"id"` + Title string `json:"title"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // ContextSource indicates who added an entity to context. diff --git a/internal/powersync/extension/generate/main.go b/internal/powersync/extension/generate/main.go index 0cbf35e..798ac06 100644 --- a/internal/powersync/extension/generate/main.go +++ b/internal/powersync/extension/generate/main.go @@ -9,9 +9,12 @@ package main import ( "context" + "encoding/json" "fmt" "os" "path/filepath" + "regexp" + "strings" psapi "github.com/usetero/cli/internal/boundary/powersync" "github.com/usetero/cli/internal/config" @@ -28,22 +31,28 @@ func run() error { cfg := config.LoadCLIConfig() endpoint := cfg.PowerSyncEndpoint token := os.Getenv("POWERSYNC_API_TOKEN") + outputDir := mustGetwd() - if token == "" { - return fmt.Errorf("POWERSYNC_API_TOKEN is required\nUse: doppler run -- go generate ./internal/powersync") - } - - ctx := context.Background() - - fmt.Println("Fetching schema from PowerSync service...") - - schemaJSON, err := psapi.FetchSchemaJSON(ctx, endpoint, token) - if err != nil { - return fmt.Errorf("fetch schema: %w", err) + var ( + schemaJSON string + err error + ) + if token != "" { + ctx := context.Background() + fmt.Println("Fetching schema from PowerSync service...") + schemaJSON, err = psapi.FetchSchemaJSON(ctx, endpoint, token) + if err != nil { + return fmt.Errorf("fetch schema: %w", err) + } + } else { + fmt.Println("POWERSYNC_API_TOKEN not set; loading schema from local control-plane snapshot...") + schemaJSON, err = loadSchemaJSONFromLocalSnapshot(outputDir) + if err != nil { + return fmt.Errorf("load local schema snapshot: %w", err) + } } // Write schema.json to the extension directory (where it's embedded from) - outputDir := mustGetwd() schemaPath := filepath.Join(outputDir, "extension", "schema.json") if err := os.WriteFile(schemaPath, []byte(schemaJSON), 0o644); err != nil { return fmt.Errorf("write schema.json: %w", err) @@ -60,3 +69,91 @@ func mustGetwd() string { } return dir } + +func loadSchemaJSONFromLocalSnapshot(outputDir string) (string, error) { + schemaPath, err := findLocalSchemaSnapshot(outputDir) + if err != nil { + return "", err + } + + schemaSQL, err := os.ReadFile(schemaPath) + if err != nil { + return "", fmt.Errorf("read %s: %w", schemaPath, err) + } + + tables, err := parseSchemaSQL(string(schemaSQL)) + if err != nil { + return "", err + } + tables = psapi.ApplyClientIndexes(tables) + + data, err := json.Marshal(struct { + Tables []psapi.SchemaTable `json:"tables"` + }{Tables: tables}) + if err != nil { + return "", fmt.Errorf("marshal schema: %w", err) + } + + return string(data), nil +} + +func findLocalSchemaSnapshot(outputDir string) (string, error) { + candidates := []string{} + if fromEnv := os.Getenv("POWERSYNC_SCHEMA_SQL"); fromEnv != "" { + candidates = append(candidates, fromEnv) + } + if controlPlaneDir := os.Getenv("CONTROL_PLANE_DIR"); controlPlaneDir != "" { + candidates = append(candidates, filepath.Join(controlPlaneDir, "internal", "infra", "powersync", "schema.sql")) + } + candidates = append(candidates, + filepath.Join(filepath.Dir(outputDir), "control-plane", "internal", "infra", "powersync", "schema.sql"), + filepath.Join(outputDir, "..", "..", "..", "..", "..", "..", "src", "tero", "control-plane", "internal", "infra", "powersync", "schema.sql"), + ) + + for _, candidate := range candidates { + candidate = filepath.Clean(candidate) + if _, err := os.Stat(candidate); err == nil { + return candidate, nil + } + } + + return "", fmt.Errorf("could not find control-plane PowerSync schema.sql; set POWERSYNC_API_TOKEN, POWERSYNC_SCHEMA_SQL, or CONTROL_PLANE_DIR") +} + +var ( + createTablePattern = regexp.MustCompile(`(?ms)^CREATE TABLE (\w+) \(\n(.*?)\n\);`) + columnPattern = regexp.MustCompile(`^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s+(TEXT|INTEGER|REAL|BLOB)\b`) +) + +func parseSchemaSQL(schemaSQL string) ([]psapi.SchemaTable, error) { + matches := createTablePattern.FindAllStringSubmatch(schemaSQL, -1) + if len(matches) == 0 { + return nil, fmt.Errorf("no CREATE TABLE statements found") + } + + tables := make([]psapi.SchemaTable, 0, len(matches)) + for _, match := range matches { + table := psapi.SchemaTable{Name: match[1], Indexes: []psapi.SchemaIndex{}} + for _, line := range strings.Split(match[2], "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "--") { + continue + } + line = strings.TrimSuffix(line, ",") + col := columnPattern.FindStringSubmatch(line) + if len(col) == 0 { + continue + } + table.Columns = append(table.Columns, psapi.SchemaColumn{ + Name: col[1], + Type: strings.ToLower(col[2]), + }) + } + if len(table.Columns) == 0 { + return nil, fmt.Errorf("table %s has no parsed columns", table.Name) + } + tables = append(tables, table) + } + + return tables, nil +} diff --git a/internal/powersync/extension/schema.json b/internal/powersync/extension/schema.json index cabe380..95d5725 100644 --- a/internal/powersync/extension/schema.json +++ b/internal/powersync/extension/schema.json @@ -1 +1 @@ -{"tables":[{"name":"conversation_contexts","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"added_by","type":"text"},{"name":"conversation_id","type":"text"},{"name":"created_at","type":"text"},{"name":"entity_id","type":"text"},{"name":"entity_type","type":"text"}],"indexes":[]},{"name":"conversations","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"title","type":"text"},{"name":"user_id","type":"text"},{"name":"view_id","type":"text"},{"name":"workspace_id","type":"text"}],"indexes":[{"name":"account_id","columns":[{"name":"account_id","ascending":true,"type":"text"}]}]},{"name":"datadog_account_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"datadog_account_id","type":"text"},{"name":"disabled_services","type":"integer"},{"name":"estimated_bytes_reduction_per_hour","type":"real"},{"name":"estimated_cost_reduction_per_hour_bytes_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_volume_usd","type":"real"},{"name":"estimated_volume_reduction_per_hour","type":"real"},{"name":"health","type":"text"},{"name":"inactive_services","type":"integer"},{"name":"log_active_services","type":"integer"},{"name":"log_event_analyzed_count","type":"integer"},{"name":"log_event_bytes_per_hour","type":"real"},{"name":"log_event_cost_per_hour_bytes_usd","type":"real"},{"name":"log_event_cost_per_hour_usd","type":"real"},{"name":"log_event_cost_per_hour_volume_usd","type":"real"},{"name":"log_event_count","type":"integer"},{"name":"log_event_volume_per_hour","type":"real"},{"name":"log_service_count","type":"integer"},{"name":"observed_bytes_per_hour_after","type":"real"},{"name":"observed_bytes_per_hour_before","type":"real"},{"name":"observed_cost_per_hour_after_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_after_usd","type":"real"},{"name":"observed_cost_per_hour_after_volume_usd","type":"real"},{"name":"observed_cost_per_hour_before_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_before_usd","type":"real"},{"name":"observed_cost_per_hour_before_volume_usd","type":"real"},{"name":"observed_volume_per_hour_after","type":"real"},{"name":"observed_volume_per_hour_before","type":"real"},{"name":"ok_services","type":"integer"},{"name":"policy_approved_count","type":"integer"},{"name":"policy_dismissed_count","type":"integer"},{"name":"policy_pending_count","type":"integer"},{"name":"policy_pending_critical_count","type":"integer"},{"name":"policy_pending_high_count","type":"integer"},{"name":"policy_pending_low_count","type":"integer"},{"name":"policy_pending_medium_count","type":"integer"},{"name":"ready_for_use","type":"integer"},{"name":"service_cost_per_hour_volume_usd","type":"real"},{"name":"service_volume_per_hour","type":"real"}],"indexes":[{"name":"datadog_account_id","columns":[{"name":"datadog_account_id","ascending":true,"type":"text"}]}]},{"name":"datadog_accounts","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"cost_per_gb_ingested","type":"real"},{"name":"created_at","type":"text"},{"name":"name","type":"text"},{"name":"site","type":"text"}],"indexes":[]},{"name":"datadog_log_indexes","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"cost_per_million_events_indexed","type":"real"},{"name":"created_at","type":"text"},{"name":"datadog_account_id","type":"text"},{"name":"name","type":"text"}],"indexes":[]},{"name":"log_event_fields","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"baseline_avg_bytes","type":"real"},{"name":"created_at","type":"text"},{"name":"field_path","type":"text"},{"name":"log_event_id","type":"text"},{"name":"value_distribution","type":"text"}],"indexes":[]},{"name":"log_event_policies","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"action","type":"text"},{"name":"analysis","type":"text"},{"name":"approved_at","type":"text"},{"name":"approved_baseline_avg_bytes","type":"real"},{"name":"approved_baseline_volume_per_hour","type":"real"},{"name":"approved_by","type":"text"},{"name":"category","type":"text"},{"name":"category_type","type":"text"},{"name":"created_at","type":"text"},{"name":"dismissed_at","type":"text"},{"name":"dismissed_by","type":"text"},{"name":"log_event_id","type":"text"},{"name":"severity","type":"text"},{"name":"subjective","type":"integer"},{"name":"workspace_id","type":"text"}],"indexes":[{"name":"log_event_id","columns":[{"name":"log_event_id","ascending":true,"type":"text"}]}]},{"name":"log_event_policy_category_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"action","type":"text"},{"name":"approved_count","type":"integer"},{"name":"boundary","type":"text"},{"name":"category","type":"text"},{"name":"category_type","type":"text"},{"name":"dismissed_count","type":"integer"},{"name":"display_name","type":"text"},{"name":"estimated_bytes_reduction_per_hour","type":"real"},{"name":"estimated_cost_reduction_per_hour_bytes_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_volume_usd","type":"real"},{"name":"estimated_volume_reduction_per_hour","type":"real"},{"name":"events_with_volumes","type":"integer"},{"name":"pending_count","type":"integer"},{"name":"policy_pending_critical_count","type":"integer"},{"name":"policy_pending_high_count","type":"integer"},{"name":"policy_pending_low_count","type":"integer"},{"name":"policy_pending_medium_count","type":"integer"},{"name":"principle","type":"text"},{"name":"subjective","type":"integer"},{"name":"total_event_count","type":"integer"}],"indexes":[]},{"name":"log_event_policy_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"action","type":"text"},{"name":"approved_at","type":"text"},{"name":"bytes_per_hour","type":"real"},{"name":"category","type":"text"},{"name":"category_type","type":"text"},{"name":"created_at","type":"text"},{"name":"dismissed_at","type":"text"},{"name":"estimated_bytes_reduction_per_hour","type":"real"},{"name":"estimated_cost_reduction_per_hour_bytes_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_volume_usd","type":"real"},{"name":"estimated_volume_reduction_per_hour","type":"real"},{"name":"log_event_id","type":"text"},{"name":"log_event_name","type":"text"},{"name":"policy_id","type":"text"},{"name":"service_id","type":"text"},{"name":"service_name","type":"text"},{"name":"severity","type":"text"},{"name":"status","type":"text"},{"name":"subjective","type":"integer"},{"name":"survival_rate","type":"real"},{"name":"volume_per_hour","type":"real"},{"name":"workspace_id","type":"text"}],"indexes":[{"name":"log_event_id","columns":[{"name":"log_event_id","ascending":true,"type":"text"}]},{"name":"category_status","columns":[{"name":"category","ascending":true,"type":"text"},{"name":"status","ascending":true,"type":"text"}]}]},{"name":"log_event_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"approved_policy_count","type":"integer"},{"name":"bytes_per_hour","type":"real"},{"name":"cost_per_hour_bytes_usd","type":"real"},{"name":"cost_per_hour_usd","type":"real"},{"name":"cost_per_hour_volume_usd","type":"real"},{"name":"dismissed_policy_count","type":"integer"},{"name":"estimated_bytes_reduction_per_hour","type":"real"},{"name":"estimated_cost_reduction_per_hour_bytes_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_volume_usd","type":"real"},{"name":"estimated_volume_reduction_per_hour","type":"real"},{"name":"has_been_analyzed","type":"integer"},{"name":"has_volumes","type":"integer"},{"name":"log_event_id","type":"text"},{"name":"observed_bytes_per_hour_after","type":"real"},{"name":"observed_bytes_per_hour_before","type":"real"},{"name":"observed_cost_per_hour_after_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_after_usd","type":"real"},{"name":"observed_cost_per_hour_after_volume_usd","type":"real"},{"name":"observed_cost_per_hour_before_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_before_usd","type":"real"},{"name":"observed_cost_per_hour_before_volume_usd","type":"real"},{"name":"observed_volume_per_hour_after","type":"real"},{"name":"observed_volume_per_hour_before","type":"real"},{"name":"pending_policy_count","type":"integer"},{"name":"policy_count","type":"integer"},{"name":"policy_pending_critical_count","type":"integer"},{"name":"policy_pending_high_count","type":"integer"},{"name":"policy_pending_low_count","type":"integer"},{"name":"policy_pending_medium_count","type":"integer"},{"name":"service_id","type":"text"},{"name":"volume_per_hour","type":"real"}],"indexes":[{"name":"log_event_id","columns":[{"name":"log_event_id","ascending":true,"type":"text"}]},{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"log_events","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"baseline_avg_bytes","type":"real"},{"name":"baseline_volume_per_hour","type":"real"},{"name":"created_at","type":"text"},{"name":"description","type":"text"},{"name":"event_nature","type":"text"},{"name":"examples","type":"text"},{"name":"matchers","type":"text"},{"name":"name","type":"text"},{"name":"service_id","type":"text"},{"name":"severity","type":"text"},{"name":"signal_purpose","type":"text"}],"indexes":[{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"messages","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"content","type":"text"},{"name":"conversation_id","type":"text"},{"name":"created_at","type":"text"},{"name":"model","type":"text"},{"name":"role","type":"text"},{"name":"stop_reason","type":"text"}],"indexes":[{"name":"conversation_id","columns":[{"name":"conversation_id","ascending":true,"type":"text"}]}]},{"name":"service_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"datadog_account_id","type":"text"},{"name":"estimated_bytes_reduction_per_hour","type":"real"},{"name":"estimated_cost_reduction_per_hour_bytes_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_usd","type":"real"},{"name":"estimated_cost_reduction_per_hour_volume_usd","type":"real"},{"name":"estimated_volume_reduction_per_hour","type":"real"},{"name":"health","type":"text"},{"name":"log_event_analyzed_count","type":"integer"},{"name":"log_event_bytes_per_hour","type":"real"},{"name":"log_event_cost_per_hour_bytes_usd","type":"real"},{"name":"log_event_cost_per_hour_usd","type":"real"},{"name":"log_event_cost_per_hour_volume_usd","type":"real"},{"name":"log_event_count","type":"integer"},{"name":"log_event_volume_per_hour","type":"real"},{"name":"observed_bytes_per_hour_after","type":"real"},{"name":"observed_bytes_per_hour_before","type":"real"},{"name":"observed_cost_per_hour_after_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_after_usd","type":"real"},{"name":"observed_cost_per_hour_after_volume_usd","type":"real"},{"name":"observed_cost_per_hour_before_bytes_usd","type":"real"},{"name":"observed_cost_per_hour_before_usd","type":"real"},{"name":"observed_cost_per_hour_before_volume_usd","type":"real"},{"name":"observed_volume_per_hour_after","type":"real"},{"name":"observed_volume_per_hour_before","type":"real"},{"name":"policy_approved_count","type":"integer"},{"name":"policy_dismissed_count","type":"integer"},{"name":"policy_pending_count","type":"integer"},{"name":"policy_pending_critical_count","type":"integer"},{"name":"policy_pending_high_count","type":"integer"},{"name":"policy_pending_low_count","type":"integer"},{"name":"policy_pending_medium_count","type":"integer"},{"name":"service_cost_per_hour_volume_usd","type":"real"},{"name":"service_debug_volume_per_hour","type":"real"},{"name":"service_error_volume_per_hour","type":"real"},{"name":"service_id","type":"text"},{"name":"service_info_volume_per_hour","type":"real"},{"name":"service_other_volume_per_hour","type":"real"},{"name":"service_volume_per_hour","type":"real"},{"name":"service_warn_volume_per_hour","type":"real"}],"indexes":[{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"services","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"description","type":"text"},{"name":"enabled","type":"integer"},{"name":"initial_weekly_log_count","type":"integer"},{"name":"name","type":"text"}],"indexes":[{"name":"name","columns":[{"name":"name","ascending":true,"type":"text"}]}]},{"name":"teams","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"name","type":"text"},{"name":"workspace_id","type":"text"}],"indexes":[]},{"name":"view_favorites","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"user_id","type":"text"},{"name":"view_id","type":"text"}],"indexes":[]},{"name":"views","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"conversation_id","type":"text"},{"name":"created_at","type":"text"},{"name":"created_by","type":"text"},{"name":"entity_type","type":"text"},{"name":"forked_from_id","type":"text"},{"name":"message_id","type":"text"},{"name":"query","type":"text"}],"indexes":[]},{"name":"workspaces","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"name","type":"text"},{"name":"purpose","type":"text"}],"indexes":[]}]} \ No newline at end of file +{"tables":[{"name":"conversations","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"title","type":"text"},{"name":"user_id","type":"text"}],"indexes":[{"name":"account_id","columns":[{"name":"account_id","ascending":true,"type":"text"}]}]},{"name":"datadog_account_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"current_bytes_per_hour","type":"real"},{"name":"current_bytes_usd_per_hour","type":"real"},{"name":"current_events_per_hour","type":"real"},{"name":"current_service_events_per_hour","type":"real"},{"name":"current_service_volume_usd_per_hour","type":"real"},{"name":"current_total_usd_per_hour","type":"real"},{"name":"current_volume_usd_per_hour","type":"real"},{"name":"datadog_account_id","type":"text"},{"name":"disabled_services","type":"integer"},{"name":"effective_bytes_per_hour","type":"real"},{"name":"effective_bytes_usd_per_hour","type":"real"},{"name":"effective_events_per_hour","type":"real"},{"name":"effective_log_event_count","type":"integer"},{"name":"effective_saved_bytes_per_hour","type":"real"},{"name":"effective_saved_bytes_usd_per_hour","type":"real"},{"name":"effective_saved_events_per_hour","type":"real"},{"name":"effective_saved_total_usd_per_hour","type":"real"},{"name":"effective_saved_volume_usd_per_hour","type":"real"},{"name":"effective_total_usd_per_hour","type":"real"},{"name":"effective_volume_usd_per_hour","type":"real"},{"name":"health","type":"text"},{"name":"inactive_services","type":"integer"},{"name":"log_active_services","type":"integer"},{"name":"log_event_analyzed_count","type":"integer"},{"name":"log_event_count","type":"integer"},{"name":"log_service_count","type":"integer"},{"name":"ok_services","type":"integer"},{"name":"preview_bytes_per_hour","type":"real"},{"name":"preview_bytes_usd_per_hour","type":"real"},{"name":"preview_events_per_hour","type":"real"},{"name":"preview_log_event_count","type":"integer"},{"name":"preview_saved_bytes_per_hour","type":"real"},{"name":"preview_saved_bytes_usd_per_hour","type":"real"},{"name":"preview_saved_events_per_hour","type":"real"},{"name":"preview_saved_total_usd_per_hour","type":"real"},{"name":"preview_saved_volume_usd_per_hour","type":"real"},{"name":"preview_total_usd_per_hour","type":"real"},{"name":"preview_volume_usd_per_hour","type":"real"},{"name":"ready_for_use","type":"integer"}],"indexes":[{"name":"datadog_account_id","columns":[{"name":"datadog_account_id","ascending":true,"type":"text"}]}]},{"name":"datadog_accounts","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"cost_per_gb_ingested","type":"real"},{"name":"created_at","type":"text"},{"name":"name","type":"text"},{"name":"site","type":"text"}],"indexes":[]},{"name":"datadog_log_indexes","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"cost_per_million_events_indexed","type":"real"},{"name":"created_at","type":"text"},{"name":"datadog_account_id","type":"text"},{"name":"name","type":"text"}],"indexes":[]},{"name":"finding_curations","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"body","type":"text"},{"name":"created_at","type":"text"},{"name":"disposition","type":"text"},{"name":"finding_id","type":"text"},{"name":"finding_problem_version","type":"integer"},{"name":"priority","type":"text"},{"name":"title","type":"text"},{"name":"version","type":"integer"}],"indexes":[]},{"name":"finding_log_events","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"finding_id","type":"text"},{"name":"log_event_id","type":"text"}],"indexes":[]},{"name":"finding_plans","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"finding_curation_id","type":"text"},{"name":"finding_id","type":"text"},{"name":"open_questions","type":"text"},{"name":"rationale","type":"text"},{"name":"revision","type":"integer"},{"name":"status","type":"text"},{"name":"steps","type":"text"},{"name":"summary","type":"text"},{"name":"title","type":"text"},{"name":"version","type":"integer"}],"indexes":[]},{"name":"finding_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"current_bytes_per_hour","type":"real"},{"name":"current_bytes_usd_per_hour","type":"real"},{"name":"current_events_per_hour","type":"real"},{"name":"current_total_usd_per_hour","type":"real"},{"name":"current_volume_usd_per_hour","type":"real"},{"name":"finding_created_at","type":"text"},{"name":"finding_id","type":"text"},{"name":"finding_updated_at","type":"text"},{"name":"isolated_bytes_per_hour","type":"real"},{"name":"isolated_bytes_usd_per_hour","type":"real"},{"name":"isolated_events_per_hour","type":"real"},{"name":"isolated_saved_bytes_per_hour","type":"real"},{"name":"isolated_saved_bytes_usd_per_hour","type":"real"},{"name":"isolated_saved_events_per_hour","type":"real"},{"name":"isolated_saved_total_usd_per_hour","type":"real"},{"name":"isolated_saved_volume_usd_per_hour","type":"real"},{"name":"isolated_total_usd_per_hour","type":"real"},{"name":"isolated_volume_usd_per_hour","type":"real"},{"name":"log_event_count","type":"integer"},{"name":"log_event_id","type":"text"},{"name":"plan_status","type":"text"},{"name":"plan_updated_at","type":"text"},{"name":"scope_kind","type":"text"},{"name":"service_id","type":"text"}],"indexes":[]},{"name":"findings","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"closed_at","type":"text"},{"name":"created_at","type":"text"},{"name":"details","type":"text"},{"name":"domain","type":"text"},{"name":"fingerprint","type":"text"},{"name":"log_event_id","type":"text"},{"name":"problem_version","type":"integer"},{"name":"scope_kind","type":"text"},{"name":"service_id","type":"text"},{"name":"type","type":"text"}],"indexes":[]},{"name":"log_event_facts","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"fact_name","type":"text"},{"name":"log_event_id","type":"text"},{"name":"slice_name","type":"text"},{"name":"slice_version","type":"integer"},{"name":"value","type":"text"}],"indexes":[]},{"name":"log_event_policies","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"compiled_at","type":"text"},{"name":"created_at","type":"text"},{"name":"finding_id","type":"text"},{"name":"kind","type":"text"},{"name":"log_event_id","type":"text"},{"name":"spec","type":"text"}],"indexes":[{"name":"log_event_id","columns":[{"name":"log_event_id","ascending":true,"type":"text"}]}]},{"name":"log_event_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"current_bytes_per_hour","type":"real"},{"name":"current_bytes_usd_per_hour","type":"real"},{"name":"current_events_per_hour","type":"real"},{"name":"current_total_usd_per_hour","type":"real"},{"name":"current_volume_usd_per_hour","type":"real"},{"name":"effective_bytes_per_hour","type":"real"},{"name":"effective_bytes_usd_per_hour","type":"real"},{"name":"effective_events_per_hour","type":"real"},{"name":"effective_saved_bytes_per_hour","type":"real"},{"name":"effective_saved_bytes_usd_per_hour","type":"real"},{"name":"effective_saved_events_per_hour","type":"real"},{"name":"effective_saved_total_usd_per_hour","type":"real"},{"name":"effective_saved_volume_usd_per_hour","type":"real"},{"name":"effective_total_usd_per_hour","type":"real"},{"name":"effective_volume_usd_per_hour","type":"real"},{"name":"has_been_analyzed","type":"integer"},{"name":"has_effective_policy","type":"integer"},{"name":"has_preview_policy","type":"integer"},{"name":"has_volumes","type":"integer"},{"name":"log_event_id","type":"text"},{"name":"preview_bytes_per_hour","type":"real"},{"name":"preview_bytes_usd_per_hour","type":"real"},{"name":"preview_events_per_hour","type":"real"},{"name":"preview_saved_bytes_per_hour","type":"real"},{"name":"preview_saved_bytes_usd_per_hour","type":"real"},{"name":"preview_saved_events_per_hour","type":"real"},{"name":"preview_saved_total_usd_per_hour","type":"real"},{"name":"preview_saved_volume_usd_per_hour","type":"real"},{"name":"preview_total_usd_per_hour","type":"real"},{"name":"preview_volume_usd_per_hour","type":"real"},{"name":"service_id","type":"text"}],"indexes":[{"name":"log_event_id","columns":[{"name":"log_event_id","ascending":true,"type":"text"}]},{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"log_events","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"baseline_avg_bytes","type":"real"},{"name":"baseline_volume_per_hour","type":"real"},{"name":"created_at","type":"text"},{"name":"description","type":"text"},{"name":"matchers","type":"text"},{"name":"name","type":"text"},{"name":"service_id","type":"text"},{"name":"severity","type":"text"}],"indexes":[{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"messages","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"content","type":"text"},{"name":"conversation_id","type":"text"},{"name":"created_at","type":"text"},{"name":"model","type":"text"},{"name":"role","type":"text"},{"name":"stop_reason","type":"text"}],"indexes":[{"name":"conversation_id","columns":[{"name":"conversation_id","ascending":true,"type":"text"}]}]},{"name":"service_facts","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"fact_group","type":"text"},{"name":"fact_type","type":"text"},{"name":"namespace","type":"text"},{"name":"service_id","type":"text"},{"name":"value","type":"text"},{"name":"version","type":"integer"}],"indexes":[]},{"name":"service_statuses_cache","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"current_bytes_per_hour","type":"real"},{"name":"current_bytes_usd_per_hour","type":"real"},{"name":"current_events_per_hour","type":"real"},{"name":"current_service_debug_events_per_hour","type":"real"},{"name":"current_service_error_events_per_hour","type":"real"},{"name":"current_service_events_per_hour","type":"real"},{"name":"current_service_info_events_per_hour","type":"real"},{"name":"current_service_other_events_per_hour","type":"real"},{"name":"current_service_volume_usd_per_hour","type":"real"},{"name":"current_service_warn_events_per_hour","type":"real"},{"name":"current_total_usd_per_hour","type":"real"},{"name":"current_volume_usd_per_hour","type":"real"},{"name":"datadog_account_id","type":"text"},{"name":"effective_bytes_per_hour","type":"real"},{"name":"effective_bytes_usd_per_hour","type":"real"},{"name":"effective_events_per_hour","type":"real"},{"name":"effective_log_event_count","type":"integer"},{"name":"effective_saved_bytes_per_hour","type":"real"},{"name":"effective_saved_bytes_usd_per_hour","type":"real"},{"name":"effective_saved_events_per_hour","type":"real"},{"name":"effective_saved_total_usd_per_hour","type":"real"},{"name":"effective_saved_volume_usd_per_hour","type":"real"},{"name":"effective_total_usd_per_hour","type":"real"},{"name":"effective_volume_usd_per_hour","type":"real"},{"name":"health","type":"text"},{"name":"log_event_analyzed_count","type":"integer"},{"name":"log_event_count","type":"integer"},{"name":"preview_bytes_per_hour","type":"real"},{"name":"preview_bytes_usd_per_hour","type":"real"},{"name":"preview_events_per_hour","type":"real"},{"name":"preview_log_event_count","type":"integer"},{"name":"preview_saved_bytes_per_hour","type":"real"},{"name":"preview_saved_bytes_usd_per_hour","type":"real"},{"name":"preview_saved_events_per_hour","type":"real"},{"name":"preview_saved_total_usd_per_hour","type":"real"},{"name":"preview_saved_volume_usd_per_hour","type":"real"},{"name":"preview_total_usd_per_hour","type":"real"},{"name":"preview_volume_usd_per_hour","type":"real"},{"name":"service_id","type":"text"}],"indexes":[{"name":"service_id","columns":[{"name":"service_id","ascending":true,"type":"text"}]}]},{"name":"service_team_mappings","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"service_id","type":"text"},{"name":"team_id","type":"text"},{"name":"updated_at","type":"text"}],"indexes":[]},{"name":"services","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"enabled","type":"integer"},{"name":"initial_weekly_log_count","type":"integer"},{"name":"name","type":"text"}],"indexes":[{"name":"name","columns":[{"name":"name","ascending":true,"type":"text"}]}]},{"name":"team_memberships","columns":[{"name":"id","type":"text"},{"name":"created_at","type":"text"},{"name":"organization_id","type":"text"},{"name":"team_id","type":"text"},{"name":"updated_at","type":"text"},{"name":"user_id","type":"text"}],"indexes":[]},{"name":"teams","columns":[{"name":"id","type":"text"},{"name":"created_at","type":"text"},{"name":"description","type":"text"},{"name":"external_id","type":"text"},{"name":"name","type":"text"},{"name":"organization_id","type":"text"},{"name":"updated_at","type":"text"}],"indexes":[]},{"name":"workspaces","columns":[{"name":"id","type":"text"},{"name":"account_id","type":"text"},{"name":"created_at","type":"text"},{"name":"name","type":"text"},{"name":"purpose","type":"text"}],"indexes":[]}]} \ No newline at end of file diff --git a/internal/sqlite/compliance_policies.go b/internal/sqlite/compliance_policies.go index 1b7e633..7ad6d60 100644 --- a/internal/sqlite/compliance_policies.go +++ b/internal/sqlite/compliance_policies.go @@ -20,11 +20,8 @@ type compliancePoliciesImpl struct { // ListPendingPoliciesByCategory returns pending compliance policies for a specific category. func (c *compliancePoliciesImpl) ListPendingPoliciesByCategory(ctx context.Context, category domain.PolicyCategory, limit int64) ([]domain.CompliancePolicy, error) { - catStr := string(category) - rows, err := c.queries.ListPendingCompliancePoliciesByCategory(ctx, gen.ListPendingCompliancePoliciesByCategoryParams{ - Category: &catStr, - Limit: limit, - }) + _ = limit + rows, err := c.queries.ListPendingCompliancePoliciesByCategory(ctx) if err != nil { return nil, WrapSQLiteError(err, "list pending compliance policies by category") } @@ -35,7 +32,7 @@ func (c *compliancePoliciesImpl) ListPendingPoliciesByCategory(ctx context.Conte Category: category, LogEventName: row.LogEventName, ServiceName: row.ServiceName, - VolumePerHour: row.VolumePerHour, + VolumePerHour: float64Ptr(row.VolumePerHour), AnyObserved: row.AnyObserved != 0, } diff --git a/internal/sqlite/conversations.go b/internal/sqlite/conversations.go index f91b3dd..c18f54d 100644 --- a/internal/sqlite/conversations.go +++ b/internal/sqlite/conversations.go @@ -12,7 +12,7 @@ import ( // Conversations provides type-safe access to conversations. type Conversations interface { Count(ctx context.Context) (int64, error) - Create(ctx context.Context, accountID domain.AccountID, workspaceID domain.WorkspaceID) (domain.ConversationID, error) + Create(ctx context.Context, accountID domain.AccountID) (domain.ConversationID, error) UpdateTitle(ctx context.Context, id domain.ConversationID, title string) error List(ctx context.Context, accountID domain.AccountID) ([]gen.Conversation, error) Get(ctx context.Context, id domain.ConversationID) (gen.Conversation, error) @@ -34,17 +34,15 @@ func (c *conversationsImpl) Count(ctx context.Context) (int64, error) { } // Create creates a new conversation and returns its ID. -func (c *conversationsImpl) Create(ctx context.Context, accountID domain.AccountID, workspaceID domain.WorkspaceID) (domain.ConversationID, error) { +func (c *conversationsImpl) Create(ctx context.Context, accountID domain.AccountID) (domain.ConversationID, error) { convID := uuid.New().String() now := time.Now().UTC().Format(time.RFC3339) accountIDStr := accountID.String() - workspaceIDStr := workspaceID.String() err := c.write.InsertConversation(ctx, gen.InsertConversationParams{ - ID: &convID, - AccountID: &accountIDStr, - WorkspaceID: &workspaceIDStr, - CreatedAt: &now, + ID: &convID, + AccountID: &accountIDStr, + CreatedAt: &now, }) if err != nil { return "", WrapSQLiteError(err, "insert conversation") diff --git a/internal/sqlite/datadog_account_statuses_test.go b/internal/sqlite/datadog_account_statuses_test.go new file mode 100644 index 0000000..d93f8ca --- /dev/null +++ b/internal/sqlite/datadog_account_statuses_test.go @@ -0,0 +1,65 @@ +package sqlite_test + +import ( + "context" + "path/filepath" + "testing" + + "github.com/usetero/cli/internal/powersync/extension" + "github.com/usetero/cli/internal/sqlite" +) + +func TestDatadogAccountStatusesGetSummary_UsesCurrentSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + dbPath := filepath.Join(t.TempDir(), "summary.sqlite") + db, err := sqlite.Open(ctx, dbPath) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + defer db.Close() + + if err := extension.ApplySchema(ctx, db); err != nil { + t.Fatalf("ApplySchema() error = %v", err) + } + + if _, err := db.Exec(ctx, ` + INSERT INTO datadog_account_statuses_cache ( + id, + account_id, + ready_for_use, + health, + log_service_count, + log_active_services, + log_event_count, + log_event_analyzed_count + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `, "status-1", "acc-1", 1, "OK", 3, 2, 20, 12); err != nil { + t.Fatalf("insert datadog_account_statuses_cache: %v", err) + } + + summary, err := db.DatadogAccountStatuses().GetSummary(ctx) + if err != nil { + t.Fatalf("GetSummary() error = %v", err) + } + + if !summary.ReadyForUse { + t.Fatal("ReadyForUse = false, want true") + } + if summary.ServiceCount != 3 { + t.Fatalf("ServiceCount = %d, want 3", summary.ServiceCount) + } + if summary.ActiveServices != 2 { + t.Fatalf("ActiveServices = %d, want 2", summary.ActiveServices) + } + if summary.EventCount != 20 { + t.Fatalf("EventCount = %d, want 20", summary.EventCount) + } + if summary.AnalyzedCount != 12 { + t.Fatalf("AnalyzedCount = %d, want 12", summary.AnalyzedCount) + } + if summary.PendingPolicyCount != 0 || summary.ApprovedPolicyCount != 0 || summary.DismissedPolicyCount != 0 { + t.Fatalf("expected zero legacy policy counts, got pending=%d approved=%d dismissed=%d", summary.PendingPolicyCount, summary.ApprovedPolicyCount, summary.DismissedPolicyCount) + } +} diff --git a/internal/sqlite/gen/compliance_policies.sql.go b/internal/sqlite/gen/compliance_policies.sql.go index b326536..d70ddb5 100644 --- a/internal/sqlite/gen/compliance_policies.sql.go +++ b/internal/sqlite/gen/compliance_policies.sql.go @@ -11,23 +11,17 @@ import ( const countObservedPoliciesByComplianceCategory = `-- name: CountObservedPoliciesByComplianceCategory :many SELECT - leps.category, - CAST(SUM(CASE WHEN COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.' || leps.category || '.fields')) f - ), 0) = 1 THEN 1 ELSE 0 END) AS INTEGER) AS observed_count -FROM log_event_policy_statuses_cache leps -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -WHERE leps.category_type = 'compliance' AND leps.status = 'PENDING' -GROUP BY leps.category + CAST(NULL AS TEXT) AS category, + CAST(0 AS INTEGER) AS observed_count +FROM findings +WHERE 1 = 0 ` type CountObservedPoliciesByComplianceCategoryRow struct { - Category *string + Category string ObservedCount int64 } -// Returns, per compliance category, how many pending policies have observed (leaking) data. func (q *Queries) CountObservedPoliciesByComplianceCategory(ctx context.Context) ([]CountObservedPoliciesByComplianceCategoryRow, error) { rows, err := q.db.QueryContext(ctx, countObservedPoliciesByComplianceCategory) if err != nil { @@ -54,41 +48,27 @@ func (q *Queries) CountObservedPoliciesByComplianceCategory(ctx context.Context) const listPendingCompliancePoliciesByCategory = `-- name: ListPendingCompliancePoliciesByCategory :many SELECT - COALESCE(s.name, '') AS service_name, - COALESCE(le.name, '') AS log_event_name, - COALESCE(lep.analysis, '') AS analysis, - les.volume_per_hour, - CAST(COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.' || ?1 || '.fields')) f - ), 0) AS INTEGER) AS any_observed -FROM log_event_policy_statuses_cache leps -JOIN log_events le ON le.id = leps.log_event_id -JOIN services s ON s.id = le.service_id -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -LEFT JOIN log_event_statuses_cache les ON les.log_event_id = leps.log_event_id -WHERE leps.category = ?1 AND leps.status = 'PENDING' -ORDER BY any_observed DESC, les.volume_per_hour DESC -LIMIT ?2 + CAST('' AS TEXT) AS service_name, + CAST('' AS TEXT) AS log_event_name, + CAST('' AS TEXT) AS analysis, + CAST(NULL AS REAL) AS volume_per_hour, + CAST(0 AS INTEGER) AS any_observed +FROM findings +WHERE 1 = 0 ` -type ListPendingCompliancePoliciesByCategoryParams struct { - Category *string - Limit int64 -} - type ListPendingCompliancePoliciesByCategoryRow struct { ServiceName string LogEventName string Analysis string - VolumePerHour *float64 + VolumePerHour float64 AnyObserved int64 } -// Compliance policy queries for PII, Secrets, PHI, and Payment Data leakage. -// Returns pending compliance policies for a specific category, sorted by observed then volume. -func (q *Queries) ListPendingCompliancePoliciesByCategory(ctx context.Context, arg ListPendingCompliancePoliciesByCategoryParams) ([]ListPendingCompliancePoliciesByCategoryRow, error) { - rows, err := q.db.QueryContext(ctx, listPendingCompliancePoliciesByCategory, arg.Category, arg.Limit) +// Compliance policy readers are retained for legacy UI surfaces only. +// The current backend no longer materializes these category-level policy rows. +func (q *Queries) ListPendingCompliancePoliciesByCategory(ctx context.Context) ([]ListPendingCompliancePoliciesByCategoryRow, error) { + rows, err := q.db.QueryContext(ctx, listPendingCompliancePoliciesByCategory) if err != nil { return nil, err } diff --git a/internal/sqlite/gen/conversations.sql.go b/internal/sqlite/gen/conversations.sql.go index d72179a..3951ce7 100644 --- a/internal/sqlite/gen/conversations.sql.go +++ b/internal/sqlite/gen/conversations.sql.go @@ -21,7 +21,7 @@ func (q *Queries) CountConversations(ctx context.Context) (int64, error) { } const getConversation = `-- name: GetConversation :one -SELECT id, account_id, created_at, title, user_id, view_id, workspace_id FROM conversations WHERE id = ? +SELECT id, account_id, created_at, title, user_id FROM conversations WHERE id = ? ` func (q *Queries) GetConversation(ctx context.Context, id *string) (Conversation, error) { @@ -33,14 +33,12 @@ func (q *Queries) GetConversation(ctx context.Context, id *string) (Conversation &i.CreatedAt, &i.Title, &i.UserID, - &i.ViewID, - &i.WorkspaceID, ) return i, err } const getLatestConversationByAccount = `-- name: GetLatestConversationByAccount :one -SELECT id, account_id, created_at, title, user_id, view_id, workspace_id FROM conversations +SELECT id, account_id, created_at, title, user_id FROM conversations WHERE account_id = ? ORDER BY created_at DESC LIMIT 1 @@ -55,36 +53,28 @@ func (q *Queries) GetLatestConversationByAccount(ctx context.Context, accountID &i.CreatedAt, &i.Title, &i.UserID, - &i.ViewID, - &i.WorkspaceID, ) return i, err } const insertConversation = `-- name: InsertConversation :exec -INSERT INTO conversations (id, account_id, workspace_id, created_at) -VALUES (?, ?, ?, ?) +INSERT INTO conversations (id, account_id, created_at) +VALUES (?, ?, ?) ` type InsertConversationParams struct { - ID *string - AccountID *string - WorkspaceID *string - CreatedAt *string + ID *string + AccountID *string + CreatedAt *string } func (q *Queries) InsertConversation(ctx context.Context, arg InsertConversationParams) error { - _, err := q.db.ExecContext(ctx, insertConversation, - arg.ID, - arg.AccountID, - arg.WorkspaceID, - arg.CreatedAt, - ) + _, err := q.db.ExecContext(ctx, insertConversation, arg.ID, arg.AccountID, arg.CreatedAt) return err } const listConversationsByAccount = `-- name: ListConversationsByAccount :many -SELECT id, account_id, created_at, title, user_id, view_id, workspace_id FROM conversations +SELECT id, account_id, created_at, title, user_id FROM conversations WHERE account_id = ? ORDER BY created_at DESC ` @@ -104,8 +94,6 @@ func (q *Queries) ListConversationsByAccount(ctx context.Context, accountID *str &i.CreatedAt, &i.Title, &i.UserID, - &i.ViewID, - &i.WorkspaceID, ); err != nil { return nil, err } diff --git a/internal/sqlite/gen/datadog_account_statuses.sql.go b/internal/sqlite/gen/datadog_account_statuses.sql.go index 3622769..d90e39e 100644 --- a/internal/sqlite/gen/datadog_account_statuses.sql.go +++ b/internal/sqlite/gen/datadog_account_statuses.sql.go @@ -28,44 +28,45 @@ SELECT CAST(COALESCE(SUM(log_event_count), 0) AS INTEGER) AS event_count, CAST(COALESCE(SUM(log_event_analyzed_count), 0) AS INTEGER) AS analyzed_count, - -- policies - CAST(COALESCE(SUM(policy_pending_count), 0) AS INTEGER) AS pending_policy_count, - CAST(COALESCE(SUM(policy_approved_count), 0) AS INTEGER) AS approved_policy_count, - CAST(COALESCE(SUM(policy_dismissed_count), 0) AS INTEGER) AS dismissed_policy_count, - CAST(COALESCE(SUM(policy_pending_critical_count), 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(SUM(policy_pending_high_count), 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(SUM(policy_pending_medium_count), 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(SUM(policy_pending_low_count), 0) AS INTEGER) AS policy_pending_low_count, + -- policy-summary UI is still legacy-shaped; preserve zero values until it moves + -- to findings-backed aggregates. + CAST(0 AS INTEGER) AS pending_policy_count, + CAST(0 AS INTEGER) AS approved_policy_count, + CAST(0 AS INTEGER) AS dismissed_policy_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, -- estimated savings - SUM(estimated_cost_reduction_per_hour_usd) AS estimated_cost_per_hour, - SUM(estimated_cost_reduction_per_hour_bytes_usd) AS estimated_cost_per_hour_bytes, - SUM(estimated_cost_reduction_per_hour_volume_usd) AS estimated_cost_per_hour_volume, - SUM(estimated_volume_reduction_per_hour) AS estimated_volume_per_hour, - SUM(estimated_bytes_reduction_per_hour) AS estimated_bytes_per_hour, + SUM(preview_saved_total_usd_per_hour) AS estimated_cost_per_hour, + SUM(preview_saved_bytes_usd_per_hour) AS estimated_cost_per_hour_bytes, + SUM(preview_saved_volume_usd_per_hour) AS estimated_cost_per_hour_volume, + SUM(preview_saved_events_per_hour) AS estimated_volume_per_hour, + SUM(preview_saved_bytes_per_hour) AS estimated_bytes_per_hour, -- observed impact - SUM(observed_cost_per_hour_before_usd) AS observed_cost_before, - SUM(observed_cost_per_hour_before_bytes_usd) AS observed_cost_before_bytes, - SUM(observed_cost_per_hour_before_volume_usd) AS observed_cost_before_volume, - SUM(observed_cost_per_hour_after_usd) AS observed_cost_after, - SUM(observed_cost_per_hour_after_bytes_usd) AS observed_cost_after_bytes, - SUM(observed_cost_per_hour_after_volume_usd) AS observed_cost_after_volume, - SUM(observed_volume_per_hour_before) AS observed_volume_before, - SUM(observed_volume_per_hour_after) AS observed_volume_after, - SUM(observed_bytes_per_hour_before) AS observed_bytes_before, - SUM(observed_bytes_per_hour_after) AS observed_bytes_after, + SUM(current_total_usd_per_hour) AS observed_cost_before, + SUM(current_bytes_usd_per_hour) AS observed_cost_before_bytes, + SUM(current_volume_usd_per_hour) AS observed_cost_before_volume, + SUM(effective_total_usd_per_hour) AS observed_cost_after, + SUM(effective_bytes_usd_per_hour) AS observed_cost_after_bytes, + SUM(effective_volume_usd_per_hour) AS observed_cost_after_volume, + SUM(current_events_per_hour) AS observed_volume_before, + SUM(effective_events_per_hour) AS observed_volume_after, + SUM(current_bytes_per_hour) AS observed_bytes_before, + SUM(effective_bytes_per_hour) AS observed_bytes_after, -- totals - SUM(log_event_cost_per_hour_usd) AS total_cost_per_hour, - SUM(log_event_cost_per_hour_bytes_usd) AS total_cost_per_hour_bytes, - SUM(log_event_cost_per_hour_volume_usd) AS total_cost_per_hour_volume, - SUM(log_event_volume_per_hour) AS total_volume_per_hour, - SUM(log_event_bytes_per_hour) AS total_bytes_per_hour, + SUM(current_total_usd_per_hour) AS total_cost_per_hour, + SUM(current_bytes_usd_per_hour) AS total_cost_per_hour_bytes, + SUM(current_volume_usd_per_hour) AS total_cost_per_hour_volume, + SUM(current_events_per_hour) AS total_volume_per_hour, + SUM(current_bytes_per_hour) AS total_bytes_per_hour, -- service-level throughput - SUM(service_volume_per_hour) AS total_service_volume_per_hour, - SUM(service_cost_per_hour_volume_usd) AS total_service_cost_per_hour + SUM(current_service_events_per_hour) AS total_service_volume_per_hour, + SUM(current_service_volume_usd_per_hour) AS total_service_cost_per_hour FROM datadog_account_statuses_cache ` diff --git a/internal/sqlite/gen/log_event_policies.sql.go b/internal/sqlite/gen/log_event_policies.sql.go index e1548fc..c4986a6 100644 --- a/internal/sqlite/gen/log_event_policies.sql.go +++ b/internal/sqlite/gen/log_event_policies.sql.go @@ -9,23 +9,6 @@ import ( "context" ) -const approveLogEventPolicy = `-- name: ApproveLogEventPolicy :exec -UPDATE log_event_policies -SET approved_at = ?, approved_by = ? -WHERE id = ? -` - -type ApproveLogEventPolicyParams struct { - ApprovedAt *string - ApprovedBy *string - ID *string -} - -func (q *Queries) ApproveLogEventPolicy(ctx context.Context, arg ApproveLogEventPolicyParams) error { - _, err := q.db.ExecContext(ctx, approveLogEventPolicy, arg.ApprovedAt, arg.ApprovedBy, arg.ID) - return err -} - const countLogEventPolicies = `-- name: CountLogEventPolicies :one SELECT COUNT(*) FROM log_event_policies ` @@ -36,20 +19,3 @@ func (q *Queries) CountLogEventPolicies(ctx context.Context) (int64, error) { err := row.Scan(&count) return count, err } - -const dismissLogEventPolicy = `-- name: DismissLogEventPolicy :exec -UPDATE log_event_policies -SET dismissed_at = ?, dismissed_by = ? -WHERE id = ? -` - -type DismissLogEventPolicyParams struct { - DismissedAt *string - DismissedBy *string - ID *string -} - -func (q *Queries) DismissLogEventPolicy(ctx context.Context, arg DismissLogEventPolicyParams) error { - _, err := q.db.ExecContext(ctx, dismissLogEventPolicy, arg.DismissedAt, arg.DismissedBy, arg.ID) - return err -} diff --git a/internal/sqlite/gen/log_event_policy_category_statuses.sql.go b/internal/sqlite/gen/log_event_policy_category_statuses.sql.go index 076d4a0..ae49dd4 100644 --- a/internal/sqlite/gen/log_event_policy_category_statuses.sql.go +++ b/internal/sqlite/gen/log_event_policy_category_statuses.sql.go @@ -11,29 +11,27 @@ import ( const listCategoryStatusesByCostAndType = `-- name: ListCategoryStatusesByCostAndType :many SELECT - COALESCE(category, '') AS category, - COALESCE(category_type, '') AS category_type, - COALESCE("action", '') AS policy_action, - COALESCE(display_name, '') AS display_name, - COALESCE(principle, '') AS principle, - CAST(COALESCE(pending_count, 0) AS INTEGER) AS pending_count, - CAST(COALESCE(approved_count, 0) AS INTEGER) AS approved_count, - CAST(COALESCE(dismissed_count, 0) AS INTEGER) AS dismissed_count, - estimated_volume_reduction_per_hour, - estimated_bytes_reduction_per_hour, - estimated_cost_reduction_per_hour_usd, - estimated_cost_reduction_per_hour_bytes_usd, - estimated_cost_reduction_per_hour_volume_usd, - CAST(COALESCE(events_with_volumes, 0) AS INTEGER) AS events_with_volumes, - CAST(COALESCE(total_event_count, 0) AS INTEGER) AS total_event_count, - CAST(COALESCE(policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count -FROM log_event_policy_category_statuses_cache -WHERE category IS NOT NULL AND category != '' - AND category_type = ?1 -ORDER BY estimated_cost_reduction_per_hour_usd DESC NULLS LAST, pending_count DESC + CAST('' AS TEXT) AS category, + CAST('' AS TEXT) AS category_type, + CAST('' AS TEXT) AS policy_action, + CAST('' AS TEXT) AS display_name, + CAST('' AS TEXT) AS principle, + CAST(0 AS INTEGER) AS pending_count, + CAST(0 AS INTEGER) AS approved_count, + CAST(0 AS INTEGER) AS dismissed_count, + CAST(NULL AS REAL) AS estimated_volume_reduction_per_hour, + CAST(NULL AS REAL) AS estimated_bytes_reduction_per_hour, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_usd, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_bytes_usd, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_volume_usd, + CAST(0 AS INTEGER) AS events_with_volumes, + CAST(0 AS INTEGER) AS total_event_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count +FROM findings +WHERE 1 = 0 ` type ListCategoryStatusesByCostAndTypeRow struct { @@ -45,11 +43,11 @@ type ListCategoryStatusesByCostAndTypeRow struct { PendingCount int64 ApprovedCount int64 DismissedCount int64 - EstimatedVolumeReductionPerHour *float64 - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 + EstimatedVolumeReductionPerHour float64 + EstimatedBytesReductionPerHour float64 + EstimatedCostReductionPerHourUsd float64 + EstimatedCostReductionPerHourBytesUsd float64 + EstimatedCostReductionPerHourVolumeUsd float64 EventsWithVolumes int64 TotalEventCount int64 PolicyPendingCriticalCount int64 @@ -58,9 +56,10 @@ type ListCategoryStatusesByCostAndTypeRow struct { PolicyPendingLowCount int64 } -// Pre-computed per-category rollup filtered by category_type (waste or compliance). -func (q *Queries) ListCategoryStatusesByCostAndType(ctx context.Context, categoryType *string) ([]ListCategoryStatusesByCostAndTypeRow, error) { - rows, err := q.db.QueryContext(ctx, listCategoryStatusesByCostAndType, categoryType) +// Legacy category rollup surface. Findings now drive issue discovery, so keep +// the old category tabs empty until they are reworked on top of findings. +func (q *Queries) ListCategoryStatusesByCostAndType(ctx context.Context) ([]ListCategoryStatusesByCostAndTypeRow, error) { + rows, err := q.db.QueryContext(ctx, listCategoryStatusesByCostAndType) if err != nil { return nil, err } diff --git a/internal/sqlite/gen/log_event_policy_statuses.sql.go b/internal/sqlite/gen/log_event_policy_statuses.sql.go index b6bb64d..c15b789 100644 --- a/internal/sqlite/gen/log_event_policy_statuses.sql.go +++ b/internal/sqlite/gen/log_event_policy_statuses.sql.go @@ -10,8 +10,7 @@ import ( ) const countFixedPIIPolicies = `-- name: CountFixedPIIPolicies :one -SELECT CAST(COUNT(*) AS INTEGER) FROM log_event_policy_statuses_cache -WHERE category = 'pii_leakage' AND status = 'APPROVED' +SELECT CAST(0 AS INTEGER) ` func (q *Queries) CountFixedPIIPolicies(ctx context.Context) (int64, error) { @@ -21,95 +20,37 @@ func (q *Queries) CountFixedPIIPolicies(ctx context.Context) (int64, error) { return column_1, err } -const listPendingPIIPolicies = `-- name: ListPendingPIIPolicies :many -SELECT - COALESCE(service_name, '') AS service_name, - COALESCE(log_event_name, '') AS log_event_name, - COALESCE(lep.analysis, '') AS analysis, - volume_per_hour, - CAST(COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.pii_leakage.fields')) f - ), 0) AS INTEGER) AS any_observed -FROM log_event_policy_statuses_cache leps -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -WHERE leps.category = 'pii_leakage' AND leps.status = 'PENDING' -ORDER BY any_observed DESC, leps.volume_per_hour DESC -` - -type ListPendingPIIPoliciesRow struct { - ServiceName string - LogEventName string - Analysis string - VolumePerHour *float64 - AnyObserved int64 -} - -func (q *Queries) ListPendingPIIPolicies(ctx context.Context) ([]ListPendingPIIPoliciesRow, error) { - rows, err := q.db.QueryContext(ctx, listPendingPIIPolicies) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListPendingPIIPoliciesRow - for rows.Next() { - var i ListPendingPIIPoliciesRow - if err := rows.Scan( - &i.ServiceName, - &i.LogEventName, - &i.Analysis, - &i.VolumePerHour, - &i.AnyObserved, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const listTopPendingPoliciesByCategory = `-- name: ListTopPendingPoliciesByCategory :many SELECT - COALESCE(service_name, '') AS service_name, - COALESCE(log_event_name, '') AS log_event_name, - volume_per_hour, - bytes_per_hour, - estimated_cost_reduction_per_hour_usd AS estimated_cost_per_hour, - estimated_cost_reduction_per_hour_bytes_usd AS estimated_cost_per_hour_bytes, - estimated_cost_reduction_per_hour_volume_usd AS estimated_cost_per_hour_volume, - estimated_bytes_reduction_per_hour AS estimated_bytes_per_hour, - estimated_volume_reduction_per_hour AS estimated_volume_per_hour -FROM log_event_policy_statuses_cache -WHERE category = ?1 AND status = 'PENDING' -ORDER BY estimated_cost_reduction_per_hour_usd DESC, volume_per_hour DESC -LIMIT ?2 + CAST('' AS TEXT) AS service_name, + CAST('' AS TEXT) AS log_event_name, + CAST(NULL AS REAL) AS volume_per_hour, + CAST(NULL AS REAL) AS bytes_per_hour, + CAST(NULL AS REAL) AS estimated_cost_per_hour, + CAST(NULL AS REAL) AS estimated_cost_per_hour_bytes, + CAST(NULL AS REAL) AS estimated_cost_per_hour_volume, + CAST(NULL AS REAL) AS estimated_bytes_per_hour, + CAST(NULL AS REAL) AS estimated_volume_per_hour +FROM findings +WHERE 1 = 0 ` -type ListTopPendingPoliciesByCategoryParams struct { - Category *string - Limit int64 -} - type ListTopPendingPoliciesByCategoryRow struct { ServiceName string LogEventName string - VolumePerHour *float64 - BytesPerHour *float64 - EstimatedCostPerHour *float64 - EstimatedCostPerHourBytes *float64 - EstimatedCostPerHourVolume *float64 - EstimatedBytesPerHour *float64 - EstimatedVolumePerHour *float64 + VolumePerHour float64 + BytesPerHour float64 + EstimatedCostPerHour float64 + EstimatedCostPerHourBytes float64 + EstimatedCostPerHourVolume float64 + EstimatedBytesPerHour float64 + EstimatedVolumePerHour float64 } -func (q *Queries) ListTopPendingPoliciesByCategory(ctx context.Context, arg ListTopPendingPoliciesByCategoryParams) ([]ListTopPendingPoliciesByCategoryRow, error) { - rows, err := q.db.QueryContext(ctx, listTopPendingPoliciesByCategory, arg.Category, arg.Limit) +// Legacy policy-category surface. The current backend is findings-first, so +// this reader intentionally returns no rows until the UI is migrated. +func (q *Queries) ListTopPendingPoliciesByCategory(ctx context.Context) ([]ListTopPendingPoliciesByCategoryRow, error) { + rows, err := q.db.QueryContext(ctx, listTopPendingPoliciesByCategory) if err != nil { return nil, err } diff --git a/internal/sqlite/gen/log_event_statuses.sql.go b/internal/sqlite/gen/log_event_statuses.sql.go index a845488..c9611e4 100644 --- a/internal/sqlite/gen/log_event_statuses.sql.go +++ b/internal/sqlite/gen/log_event_statuses.sql.go @@ -12,20 +12,20 @@ import ( const listLogEventStatusesByService = `-- name: ListLogEventStatusesByService :many SELECT COALESCE(le.name, '') AS log_event_name, - les.volume_per_hour, - les.bytes_per_hour, - les.cost_per_hour_usd, - CAST(COALESCE(les.pending_policy_count, 0) AS INTEGER) AS pending_policy_count, - CAST(COALESCE(les.approved_policy_count, 0) AS INTEGER) AS approved_policy_count, - CAST(COALESCE(les.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(les.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(les.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(les.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count + les.current_events_per_hour AS volume_per_hour, + les.current_bytes_per_hour AS bytes_per_hour, + les.current_total_usd_per_hour AS cost_per_hour_usd, + CAST(0 AS INTEGER) AS pending_policy_count, + CAST(0 AS INTEGER) AS approved_policy_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count FROM log_events le JOIN services s ON s.id = le.service_id LEFT JOIN log_event_statuses_cache les ON les.log_event_id = le.id WHERE s.name = ?1 -ORDER BY les.cost_per_hour_usd DESC, les.volume_per_hour DESC +ORDER BY les.current_total_usd_per_hour DESC, les.current_events_per_hour DESC LIMIT ?2 ` diff --git a/internal/sqlite/gen/models.go b/internal/sqlite/gen/models.go index 56484f6..0f71d87 100644 --- a/internal/sqlite/gen/models.go +++ b/internal/sqlite/gen/models.go @@ -5,23 +5,11 @@ package gen type Conversation struct { - ID *string - AccountID *string - CreatedAt *string - Title *string - UserID *string - ViewID *string - WorkspaceID *string -} - -type ConversationContext struct { - ID *string - AccountID *string - AddedBy *string - ConversationID *string - CreatedAt *string - EntityID *string - EntityType *string + ID *string + AccountID *string + CreatedAt *string + Title *string + UserID *string } type DatadogAccount struct { @@ -34,47 +22,47 @@ type DatadogAccount struct { } type DatadogAccountStatusesCache struct { - ID *string - AccountID *string - DatadogAccountID *string - DisabledServices *int64 - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 - EstimatedVolumeReductionPerHour *float64 - Health *string - InactiveServices *int64 - LogActiveServices *int64 - LogEventAnalyzedCount *int64 - LogEventBytesPerHour *float64 - LogEventCostPerHourBytesUsd *float64 - LogEventCostPerHourUsd *float64 - LogEventCostPerHourVolumeUsd *float64 - LogEventCount *int64 - LogEventVolumePerHour *float64 - LogServiceCount *int64 - ObservedBytesPerHourAfter *float64 - ObservedBytesPerHourBefore *float64 - ObservedCostPerHourAfterBytesUsd *float64 - ObservedCostPerHourAfterUsd *float64 - ObservedCostPerHourAfterVolumeUsd *float64 - ObservedCostPerHourBeforeBytesUsd *float64 - ObservedCostPerHourBeforeUsd *float64 - ObservedCostPerHourBeforeVolumeUsd *float64 - ObservedVolumePerHourAfter *float64 - ObservedVolumePerHourBefore *float64 - OkServices *int64 - PolicyApprovedCount *int64 - PolicyDismissedCount *int64 - PolicyPendingCount *int64 - PolicyPendingCriticalCount *int64 - PolicyPendingHighCount *int64 - PolicyPendingLowCount *int64 - PolicyPendingMediumCount *int64 - ReadyForUse *int64 - ServiceCostPerHourVolumeUsd *float64 - ServiceVolumePerHour *float64 + ID *string + AccountID *string + CurrentBytesPerHour *float64 + CurrentBytesUsdPerHour *float64 + CurrentEventsPerHour *float64 + CurrentServiceEventsPerHour *float64 + CurrentServiceVolumeUsdPerHour *float64 + CurrentTotalUsdPerHour *float64 + CurrentVolumeUsdPerHour *float64 + DatadogAccountID *string + DisabledServices *int64 + EffectiveBytesPerHour *float64 + EffectiveBytesUsdPerHour *float64 + EffectiveEventsPerHour *float64 + EffectiveLogEventCount *int64 + EffectiveSavedBytesPerHour *float64 + EffectiveSavedBytesUsdPerHour *float64 + EffectiveSavedEventsPerHour *float64 + EffectiveSavedTotalUsdPerHour *float64 + EffectiveSavedVolumeUsdPerHour *float64 + EffectiveTotalUsdPerHour *float64 + EffectiveVolumeUsdPerHour *float64 + Health *string + InactiveServices *int64 + LogActiveServices *int64 + LogEventAnalyzedCount *int64 + LogEventCount *int64 + LogServiceCount *int64 + OkServices *int64 + PreviewBytesPerHour *float64 + PreviewBytesUsdPerHour *float64 + PreviewEventsPerHour *float64 + PreviewLogEventCount *int64 + PreviewSavedBytesPerHour *float64 + PreviewSavedBytesUsdPerHour *float64 + PreviewSavedEventsPerHour *float64 + PreviewSavedTotalUsdPerHour *float64 + PreviewSavedVolumeUsdPerHour *float64 + PreviewTotalUsdPerHour *float64 + PreviewVolumeUsdPerHour *float64 + ReadyForUse *int64 } type DatadogLogIndex struct { @@ -86,141 +74,156 @@ type DatadogLogIndex struct { Name *string } -type LogEvent struct { +type Finding struct { + ID *string + AccountID *string + ClosedAt *string + CreatedAt *string + Details *string + Domain *string + Fingerprint *string + LogEventID *string + ProblemVersion *int64 + ScopeKind *string + ServiceID *string + Type *string +} + +type FindingCuration struct { ID *string AccountID *string - BaselineAvgBytes *float64 - BaselineVolumePerHour *float64 + Body *string CreatedAt *string - Description *string - EventNature *string - Examples *string - Matchers *string - Name *string - ServiceID *string - Severity *string - SignalPurpose *string + Disposition *string + FindingID *string + FindingProblemVersion *int64 + Priority *string + Title *string + Version *int64 } -type LogEventField struct { +type FindingLogEvent struct { + ID *string + AccountID *string + CreatedAt *string + FindingID *string + LogEventID *string +} + +type FindingPlan struct { ID *string AccountID *string - BaselineAvgBytes *float64 CreatedAt *string - FieldPath *string - LogEventID *string - ValueDistribution *string + FindingCurationID *string + FindingID *string + OpenQuestions *string + Rationale *string + Revision *int64 + Status *string + Steps *string + Summary *string + Title *string + Version *int64 } -type LogEventPolicy struct { +type FindingStatusesCache struct { ID *string AccountID *string - Action *string - Analysis *string - ApprovedAt *string - ApprovedBaselineAvgBytes *float64 - ApprovedBaselineVolumePerHour *float64 - ApprovedBy *string - Category *string - CategoryType *string - CreatedAt *string - DismissedAt *string - DismissedBy *string + CurrentBytesPerHour *float64 + CurrentBytesUsdPerHour *float64 + CurrentEventsPerHour *float64 + CurrentTotalUsdPerHour *float64 + CurrentVolumeUsdPerHour *float64 + FindingCreatedAt *string + FindingID *string + FindingUpdatedAt *string + IsolatedBytesPerHour *float64 + IsolatedBytesUsdPerHour *float64 + IsolatedEventsPerHour *float64 + IsolatedSavedBytesPerHour *float64 + IsolatedSavedBytesUsdPerHour *float64 + IsolatedSavedEventsPerHour *float64 + IsolatedSavedTotalUsdPerHour *float64 + IsolatedSavedVolumeUsdPerHour *float64 + IsolatedTotalUsdPerHour *float64 + IsolatedVolumeUsdPerHour *float64 + LogEventCount *int64 LogEventID *string - Severity *string - Subjective *int64 - WorkspaceID *string + PlanStatus *string + PlanUpdatedAt *string + ScopeKind *string + ServiceID *string } -type LogEventPolicyCategoryStatusesCache struct { - ID *string - AccountID *string - Action *string - ApprovedCount *int64 - Boundary *string - Category *string - CategoryType *string - DismissedCount *int64 - DisplayName *string - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 - EstimatedVolumeReductionPerHour *float64 - EventsWithVolumes *int64 - PendingCount *int64 - PolicyPendingCriticalCount *int64 - PolicyPendingHighCount *int64 - PolicyPendingLowCount *int64 - PolicyPendingMediumCount *int64 - Principle *string - Subjective *int64 - TotalEventCount *int64 +type LogEvent struct { + ID *string + AccountID *string + BaselineAvgBytes *float64 + BaselineVolumePerHour *float64 + CreatedAt *string + Description *string + Matchers *string + Name *string + ServiceID *string + Severity *string } -type LogEventPolicyStatusesCache struct { - ID *string - AccountID *string - Action *string - ApprovedAt *string - BytesPerHour *float64 - Category *string - CategoryType *string - CreatedAt *string - DismissedAt *string - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 - EstimatedVolumeReductionPerHour *float64 - LogEventID *string - LogEventName *string - PolicyID *string - ServiceID *string - ServiceName *string - Severity *string - Status *string - Subjective *int64 - SurvivalRate *float64 - VolumePerHour *float64 - WorkspaceID *string +type LogEventFact struct { + ID *string + AccountID *string + CreatedAt *string + FactName *string + LogEventID *string + SliceName *string + SliceVersion *int64 + Value *string +} + +type LogEventPolicy struct { + ID *string + AccountID *string + CompiledAt *string + CreatedAt *string + FindingID *string + Kind *string + LogEventID *string + Spec *string } type LogEventStatusesCache struct { - ID *string - AccountID *string - ApprovedPolicyCount *int64 - BytesPerHour *float64 - CostPerHourBytesUsd *float64 - CostPerHourUsd *float64 - CostPerHourVolumeUsd *float64 - DismissedPolicyCount *int64 - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 - EstimatedVolumeReductionPerHour *float64 - HasBeenAnalyzed *int64 - HasVolumes *int64 - LogEventID *string - ObservedBytesPerHourAfter *float64 - ObservedBytesPerHourBefore *float64 - ObservedCostPerHourAfterBytesUsd *float64 - ObservedCostPerHourAfterUsd *float64 - ObservedCostPerHourAfterVolumeUsd *float64 - ObservedCostPerHourBeforeBytesUsd *float64 - ObservedCostPerHourBeforeUsd *float64 - ObservedCostPerHourBeforeVolumeUsd *float64 - ObservedVolumePerHourAfter *float64 - ObservedVolumePerHourBefore *float64 - PendingPolicyCount *int64 - PolicyCount *int64 - PolicyPendingCriticalCount *int64 - PolicyPendingHighCount *int64 - PolicyPendingLowCount *int64 - PolicyPendingMediumCount *int64 - ServiceID *string - VolumePerHour *float64 + ID *string + AccountID *string + CurrentBytesPerHour *float64 + CurrentBytesUsdPerHour *float64 + CurrentEventsPerHour *float64 + CurrentTotalUsdPerHour *float64 + CurrentVolumeUsdPerHour *float64 + EffectiveBytesPerHour *float64 + EffectiveBytesUsdPerHour *float64 + EffectiveEventsPerHour *float64 + EffectiveSavedBytesPerHour *float64 + EffectiveSavedBytesUsdPerHour *float64 + EffectiveSavedEventsPerHour *float64 + EffectiveSavedTotalUsdPerHour *float64 + EffectiveSavedVolumeUsdPerHour *float64 + EffectiveTotalUsdPerHour *float64 + EffectiveVolumeUsdPerHour *float64 + HasBeenAnalyzed *int64 + HasEffectivePolicy *int64 + HasPreviewPolicy *int64 + HasVolumes *int64 + LogEventID *string + PreviewBytesPerHour *float64 + PreviewBytesUsdPerHour *float64 + PreviewEventsPerHour *float64 + PreviewSavedBytesPerHour *float64 + PreviewSavedBytesUsdPerHour *float64 + PreviewSavedEventsPerHour *float64 + PreviewSavedTotalUsdPerHour *float64 + PreviewSavedVolumeUsdPerHour *float64 + PreviewTotalUsdPerHour *float64 + PreviewVolumeUsdPerHour *float64 + ServiceID *string } type Message struct { @@ -238,82 +241,93 @@ type Service struct { ID *string AccountID *string CreatedAt *string - Description *string Enabled *int64 InitialWeeklyLogCount *int64 Name *string } +type ServiceFact struct { + ID *string + AccountID *string + CreatedAt *string + FactGroup *string + FactType *string + Namespace *string + ServiceID *string + Value *string + Version *int64 +} + type ServiceStatusesCache struct { - ID *string - AccountID *string - DatadogAccountID *string - EstimatedBytesReductionPerHour *float64 - EstimatedCostReductionPerHourBytesUsd *float64 - EstimatedCostReductionPerHourUsd *float64 - EstimatedCostReductionPerHourVolumeUsd *float64 - EstimatedVolumeReductionPerHour *float64 - Health *string - LogEventAnalyzedCount *int64 - LogEventBytesPerHour *float64 - LogEventCostPerHourBytesUsd *float64 - LogEventCostPerHourUsd *float64 - LogEventCostPerHourVolumeUsd *float64 - LogEventCount *int64 - LogEventVolumePerHour *float64 - ObservedBytesPerHourAfter *float64 - ObservedBytesPerHourBefore *float64 - ObservedCostPerHourAfterBytesUsd *float64 - ObservedCostPerHourAfterUsd *float64 - ObservedCostPerHourAfterVolumeUsd *float64 - ObservedCostPerHourBeforeBytesUsd *float64 - ObservedCostPerHourBeforeUsd *float64 - ObservedCostPerHourBeforeVolumeUsd *float64 - ObservedVolumePerHourAfter *float64 - ObservedVolumePerHourBefore *float64 - PolicyApprovedCount *int64 - PolicyDismissedCount *int64 - PolicyPendingCount *int64 - PolicyPendingCriticalCount *int64 - PolicyPendingHighCount *int64 - PolicyPendingLowCount *int64 - PolicyPendingMediumCount *int64 - ServiceCostPerHourVolumeUsd *float64 - ServiceDebugVolumePerHour *float64 - ServiceErrorVolumePerHour *float64 - ServiceID *string - ServiceInfoVolumePerHour *float64 - ServiceOtherVolumePerHour *float64 - ServiceVolumePerHour *float64 - ServiceWarnVolumePerHour *float64 + ID *string + AccountID *string + CurrentBytesPerHour *float64 + CurrentBytesUsdPerHour *float64 + CurrentEventsPerHour *float64 + CurrentServiceDebugEventsPerHour *float64 + CurrentServiceErrorEventsPerHour *float64 + CurrentServiceEventsPerHour *float64 + CurrentServiceInfoEventsPerHour *float64 + CurrentServiceOtherEventsPerHour *float64 + CurrentServiceVolumeUsdPerHour *float64 + CurrentServiceWarnEventsPerHour *float64 + CurrentTotalUsdPerHour *float64 + CurrentVolumeUsdPerHour *float64 + DatadogAccountID *string + EffectiveBytesPerHour *float64 + EffectiveBytesUsdPerHour *float64 + EffectiveEventsPerHour *float64 + EffectiveLogEventCount *int64 + EffectiveSavedBytesPerHour *float64 + EffectiveSavedBytesUsdPerHour *float64 + EffectiveSavedEventsPerHour *float64 + EffectiveSavedTotalUsdPerHour *float64 + EffectiveSavedVolumeUsdPerHour *float64 + EffectiveTotalUsdPerHour *float64 + EffectiveVolumeUsdPerHour *float64 + Health *string + LogEventAnalyzedCount *int64 + LogEventCount *int64 + PreviewBytesPerHour *float64 + PreviewBytesUsdPerHour *float64 + PreviewEventsPerHour *float64 + PreviewLogEventCount *int64 + PreviewSavedBytesPerHour *float64 + PreviewSavedBytesUsdPerHour *float64 + PreviewSavedEventsPerHour *float64 + PreviewSavedTotalUsdPerHour *float64 + PreviewSavedVolumeUsdPerHour *float64 + PreviewTotalUsdPerHour *float64 + PreviewVolumeUsdPerHour *float64 + ServiceID *string } -type Team struct { - ID *string - AccountID *string - CreatedAt *string - Name *string - WorkspaceID *string +type ServiceTeamMapping struct { + ID *string + AccountID *string + CreatedAt *string + ServiceID *string + TeamID *string + UpdatedAt *string } -type View struct { +type Team struct { ID *string - AccountID *string - ConversationID *string CreatedAt *string - CreatedBy *string - EntityType *string - ForkedFromID *string - MessageID *string - Query *string + Description *string + ExternalID *string + Name *string + OrganizationID *string + UpdatedAt *string } -type ViewFavorite struct { - ID *string - AccountID *string - CreatedAt *string - UserID *string - ViewID *string +type TeamMembership struct { + ID *string + CreatedAt *string + OrganizationID *string + TeamID *string + UpdatedAt *string + UserID *string } type Workspace struct { diff --git a/internal/sqlite/gen/policy_cards.sql.go b/internal/sqlite/gen/policy_cards.sql.go index d80d7bf..c58a9d3 100644 --- a/internal/sqlite/gen/policy_cards.sql.go +++ b/internal/sqlite/gen/policy_cards.sql.go @@ -11,31 +11,31 @@ import ( const getPolicyCard = `-- name: GetPolicyCard :one SELECT - COALESCE(ps.policy_id, '') AS policy_id, - COALESCE(ps.service_name, '') AS service_name, - COALESCE(ps.log_event_name, '') AS log_event_name, - COALESCE(ps.category, '') AS category, - COALESCE(ps.category_type, '') AS category_type, - COALESCE(ps.action, '') AS "action", - COALESCE(ps.status, '') AS status, - ps.volume_per_hour, - ps.bytes_per_hour, - ps.estimated_cost_reduction_per_hour_usd AS estimated_cost_per_hour, - ps.estimated_volume_reduction_per_hour AS estimated_volume_per_hour, - ps.estimated_bytes_reduction_per_hour AS estimated_bytes_per_hour, - COALESCE(ps.severity, '') AS severity, - ps.survival_rate, - COALESCE(cat.display_name, '') AS category_display_name, - COALESCE(lep.analysis, '') AS analysis, - COALESCE(le.examples, '') AS examples, + COALESCE(lp.id, '') AS policy_id, + COALESCE(s.name, '') AS service_name, + COALESCE(le.name, '') AS log_event_name, + CAST('' AS TEXT) AS category, + CAST('' AS TEXT) AS category_type, + CAST('' AS TEXT) AS "action", + COALESCE(lp.kind, '') AS status, + les.current_events_per_hour AS volume_per_hour, + les.current_bytes_per_hour AS bytes_per_hour, + les.preview_saved_total_usd_per_hour AS estimated_cost_per_hour, + les.preview_saved_events_per_hour AS estimated_volume_per_hour, + les.preview_saved_bytes_per_hour AS estimated_bytes_per_hour, + COALESCE(le.severity, '') AS severity, + CAST(NULL AS REAL) AS survival_rate, + CAST('' AS TEXT) AS category_display_name, + COALESCE(lp.spec, '') AS analysis, + CAST('' AS TEXT) AS examples, le.baseline_avg_bytes AS event_baseline_avg_bytes, le.baseline_volume_per_hour AS event_baseline_volume_per_hour, - COALESCE(ps.log_event_id, '') AS log_event_id -FROM log_event_policy_statuses_cache ps -LEFT JOIN log_event_policy_category_statuses_cache cat ON cat.category = ps.category -LEFT JOIN log_event_policies lep ON lep.id = ps.policy_id -LEFT JOIN log_events le ON le.id = ps.log_event_id -WHERE ps.policy_id = ?1 + COALESCE(lp.log_event_id, '') AS log_event_id +FROM log_event_policies lp +LEFT JOIN log_events le ON le.id = lp.log_event_id +LEFT JOIN services s ON s.id = le.service_id +LEFT JOIN log_event_statuses_cache les ON les.log_event_id = lp.log_event_id +WHERE lp.id = ?1 ` type GetPolicyCardRow struct { @@ -52,7 +52,7 @@ type GetPolicyCardRow struct { EstimatedVolumePerHour *float64 EstimatedBytesPerHour *float64 Severity string - SurvivalRate *float64 + SurvivalRate float64 CategoryDisplayName string Analysis string Examples string @@ -61,11 +61,10 @@ type GetPolicyCardRow struct { LogEventID string } -// Fetches a single policy with all context needed for rich card rendering. -// Main table: log_event_policy_statuses_cache (denormalized policy + metrics). -// JOINs enrich with: category display name, AI analysis, log examples, baselines. -func (q *Queries) GetPolicyCard(ctx context.Context, policyID *string) (GetPolicyCardRow, error) { - row := q.db.QueryRowContext(ctx, getPolicyCard, policyID) +// Best-effort reader for the legacy policy card surface over the current +// compiled policy schema. +func (q *Queries) GetPolicyCard(ctx context.Context, id *string) (GetPolicyCardRow, error) { + row := q.db.QueryRowContext(ctx, getPolicyCard, id) var i GetPolicyCardRow err := row.Scan( &i.PolicyID, @@ -94,22 +93,19 @@ func (q *Queries) GetPolicyCard(ctx context.Context, policyID *string) (GetPolic const listFieldsByLogEvent = `-- name: ListFieldsByLogEvent :many SELECT - COALESCE(field_path, '') AS field_path, - baseline_avg_bytes -FROM log_event_fields -WHERE log_event_id = ?1 -ORDER BY baseline_avg_bytes DESC + CAST('' AS TEXT) AS field_path, + CAST(NULL AS REAL) AS baseline_avg_bytes +FROM log_event_facts +WHERE 1 = 0 ` type ListFieldsByLogEventRow struct { FieldPath string - BaselineAvgBytes *float64 + BaselineAvgBytes float64 } -// Returns per-field metadata for a log event, used to show per-field byte impact -// in quality policies (instrumentation_bloat, oversized_fields, duplicate_fields). -func (q *Queries) ListFieldsByLogEvent(ctx context.Context, logEventID *string) ([]ListFieldsByLogEventRow, error) { - rows, err := q.db.QueryContext(ctx, listFieldsByLogEvent, logEventID) +func (q *Queries) ListFieldsByLogEvent(ctx context.Context) ([]ListFieldsByLogEventRow, error) { + rows, err := q.db.QueryContext(ctx, listFieldsByLogEvent) if err != nil { return nil, err } diff --git a/internal/sqlite/gen/querier.go b/internal/sqlite/gen/querier.go index 02a3dcf..fcf67d5 100644 --- a/internal/sqlite/gen/querier.go +++ b/internal/sqlite/gen/querier.go @@ -9,48 +9,44 @@ import ( ) type Querier interface { - ApproveLogEventPolicy(ctx context.Context, arg ApproveLogEventPolicyParams) error CountConversations(ctx context.Context) (int64, error) CountFixedPIIPolicies(ctx context.Context) (int64, error) CountLogEventPolicies(ctx context.Context) (int64, error) CountLogEvents(ctx context.Context) (int64, error) CountMessages(ctx context.Context) (int64, error) CountMessagesByConversation(ctx context.Context, conversationID *string) (int64, error) - // Returns, per compliance category, how many pending policies have observed (leaking) data. CountObservedPoliciesByComplianceCategory(ctx context.Context) ([]CountObservedPoliciesByComplianceCategoryRow, error) CountServices(ctx context.Context) (int64, error) DeleteMessage(ctx context.Context, id *string) error - DismissLogEventPolicy(ctx context.Context, arg DismissLogEventPolicyParams) error GetAccountSummary(ctx context.Context) (GetAccountSummaryRow, error) GetConversation(ctx context.Context, id *string) (Conversation, error) GetLatestConversationByAccount(ctx context.Context, accountID *string) (Conversation, error) GetLatestMessageByConversation(ctx context.Context, conversationID *string) (Message, error) GetMessage(ctx context.Context, id *string) (Message, error) - // Fetches a single policy with all context needed for rich card rendering. - // Main table: log_event_policy_statuses_cache (denormalized policy + metrics). - // JOINs enrich with: category display name, AI analysis, log examples, baselines. - GetPolicyCard(ctx context.Context, policyID *string) (GetPolicyCardRow, error) + // Best-effort reader for the legacy policy card surface over the current + // compiled policy schema. + GetPolicyCard(ctx context.Context, id *string) (GetPolicyCardRow, error) GetService(ctx context.Context, id *string) (Service, error) InsertConversation(ctx context.Context, arg InsertConversationParams) error InsertMessage(ctx context.Context, arg InsertMessageParams) error ListAllServiceStatuses(ctx context.Context) ([]ListAllServiceStatusesRow, error) - // Pre-computed per-category rollup filtered by category_type (waste or compliance). - ListCategoryStatusesByCostAndType(ctx context.Context, categoryType *string) ([]ListCategoryStatusesByCostAndTypeRow, error) + // Legacy category rollup surface. Findings now drive issue discovery, so keep + // the old category tabs empty until they are reworked on top of findings. + ListCategoryStatusesByCostAndType(ctx context.Context) ([]ListCategoryStatusesByCostAndTypeRow, error) ListConversationsByAccount(ctx context.Context, accountID *string) ([]Conversation, error) ListEnabledServiceStatuses(ctx context.Context, rowLimit int64) ([]ListEnabledServiceStatusesRow, error) - // Returns per-field metadata for a log event, used to show per-field byte impact - // in quality policies (instrumentation_bloat, oversized_fields, duplicate_fields). - ListFieldsByLogEvent(ctx context.Context, logEventID *string) ([]ListFieldsByLogEventRow, error) + ListFieldsByLogEvent(ctx context.Context) ([]ListFieldsByLogEventRow, error) ListLogEventStatusesByService(ctx context.Context, arg ListLogEventStatusesByServiceParams) ([]ListLogEventStatusesByServiceRow, error) ListMessagesByConversation(ctx context.Context, conversationID *string) ([]Message, error) ListMessagesByConversationDesc(ctx context.Context, conversationID *string) ([]Message, error) - // Compliance policy queries for PII, Secrets, PHI, and Payment Data leakage. - // Returns pending compliance policies for a specific category, sorted by observed then volume. - ListPendingCompliancePoliciesByCategory(ctx context.Context, arg ListPendingCompliancePoliciesByCategoryParams) ([]ListPendingCompliancePoliciesByCategoryRow, error) - ListPendingPIIPolicies(ctx context.Context) ([]ListPendingPIIPoliciesRow, error) + // Compliance policy readers are retained for legacy UI surfaces only. + // The current backend no longer materializes these category-level policy rows. + ListPendingCompliancePoliciesByCategory(ctx context.Context) ([]ListPendingCompliancePoliciesByCategoryRow, error) ListServices(ctx context.Context) ([]Service, error) ListServicesByAccount(ctx context.Context, accountID *string) ([]Service, error) - ListTopPendingPoliciesByCategory(ctx context.Context, arg ListTopPendingPoliciesByCategoryParams) ([]ListTopPendingPoliciesByCategoryRow, error) + // Legacy policy-category surface. The current backend is findings-first, so + // this reader intentionally returns no rows until the UI is migrated. + ListTopPendingPoliciesByCategory(ctx context.Context) ([]ListTopPendingPoliciesByCategoryRow, error) SetServiceEnabled(ctx context.Context, arg SetServiceEnabledParams) error UpdateConversationTitle(ctx context.Context, arg UpdateConversationTitleParams) error UpdateMessageContent(ctx context.Context, arg UpdateMessageContentParams) error diff --git a/internal/sqlite/gen/service_statuses.sql.go b/internal/sqlite/gen/service_statuses.sql.go index b748e21..d9740f7 100644 --- a/internal/sqlite/gen/service_statuses.sql.go +++ b/internal/sqlite/gen/service_statuses.sql.go @@ -15,40 +15,40 @@ SELECT COALESCE(ssc.health, '') AS health, CAST(COALESCE(ssc.log_event_count, 0) AS INTEGER) AS log_event_count, CAST(COALESCE(ssc.log_event_analyzed_count, 0) AS INTEGER) AS log_event_analyzed_count, - CAST(COALESCE(ssc.policy_pending_count, 0) AS INTEGER) AS policy_pending_count, - CAST(COALESCE(ssc.policy_approved_count, 0) AS INTEGER) AS policy_approved_count, - CAST(COALESCE(ssc.policy_dismissed_count, 0) AS INTEGER) AS policy_dismissed_count, - CAST(COALESCE(ssc.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(ssc.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(ssc.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(ssc.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count, - ssc.service_volume_per_hour, - ssc.service_debug_volume_per_hour, - ssc.service_info_volume_per_hour, - ssc.service_warn_volume_per_hour, - ssc.service_error_volume_per_hour, - ssc.service_other_volume_per_hour, - ssc.service_cost_per_hour_volume_usd, - ssc.log_event_volume_per_hour, - ssc.log_event_bytes_per_hour, - ssc.log_event_cost_per_hour_usd, - ssc.log_event_cost_per_hour_bytes_usd, - ssc.log_event_cost_per_hour_volume_usd, - ssc.estimated_volume_reduction_per_hour, - ssc.estimated_bytes_reduction_per_hour, - ssc.estimated_cost_reduction_per_hour_usd, - ssc.estimated_cost_reduction_per_hour_bytes_usd, - ssc.estimated_cost_reduction_per_hour_volume_usd, - ssc.observed_volume_per_hour_before, - ssc.observed_volume_per_hour_after, - ssc.observed_bytes_per_hour_before, - ssc.observed_bytes_per_hour_after, - ssc.observed_cost_per_hour_before_usd, - ssc.observed_cost_per_hour_before_bytes_usd, - ssc.observed_cost_per_hour_before_volume_usd, - ssc.observed_cost_per_hour_after_usd, - ssc.observed_cost_per_hour_after_bytes_usd, - ssc.observed_cost_per_hour_after_volume_usd + CAST(0 AS INTEGER) AS policy_pending_count, + CAST(0 AS INTEGER) AS policy_approved_count, + CAST(0 AS INTEGER) AS policy_dismissed_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, + ssc.current_service_events_per_hour AS service_volume_per_hour, + ssc.current_service_debug_events_per_hour AS service_debug_volume_per_hour, + ssc.current_service_info_events_per_hour AS service_info_volume_per_hour, + ssc.current_service_warn_events_per_hour AS service_warn_volume_per_hour, + ssc.current_service_error_events_per_hour AS service_error_volume_per_hour, + ssc.current_service_other_events_per_hour AS service_other_volume_per_hour, + ssc.current_service_volume_usd_per_hour AS service_cost_per_hour_volume_usd, + ssc.current_events_per_hour AS log_event_volume_per_hour, + ssc.current_bytes_per_hour AS log_event_bytes_per_hour, + ssc.current_total_usd_per_hour AS log_event_cost_per_hour_usd, + ssc.current_bytes_usd_per_hour AS log_event_cost_per_hour_bytes_usd, + ssc.current_volume_usd_per_hour AS log_event_cost_per_hour_volume_usd, + ssc.preview_saved_events_per_hour AS estimated_volume_reduction_per_hour, + ssc.preview_saved_bytes_per_hour AS estimated_bytes_reduction_per_hour, + ssc.preview_saved_total_usd_per_hour AS estimated_cost_reduction_per_hour_usd, + ssc.preview_saved_bytes_usd_per_hour AS estimated_cost_reduction_per_hour_bytes_usd, + ssc.preview_saved_volume_usd_per_hour AS estimated_cost_reduction_per_hour_volume_usd, + CAST(NULL AS REAL) AS observed_volume_per_hour_before, + ssc.effective_events_per_hour AS observed_volume_per_hour_after, + CAST(NULL AS REAL) AS observed_bytes_per_hour_before, + ssc.effective_bytes_per_hour AS observed_bytes_per_hour_after, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_bytes_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_volume_usd, + ssc.effective_total_usd_per_hour AS observed_cost_per_hour_after_usd, + ssc.effective_bytes_usd_per_hour AS observed_cost_per_hour_after_bytes_usd, + ssc.effective_volume_usd_per_hour AS observed_cost_per_hour_after_volume_usd FROM service_statuses_cache ssc JOIN services s ON ssc.service_id = s.id ORDER BY @@ -58,7 +58,7 @@ ORDER BY WHEN 'INACTIVE' THEN 3 ELSE 4 END, - ssc.log_event_cost_per_hour_usd DESC, + ssc.current_total_usd_per_hour DESC, s.name ` @@ -91,13 +91,13 @@ type ListAllServiceStatusesRow struct { EstimatedCostReductionPerHourUsd *float64 EstimatedCostReductionPerHourBytesUsd *float64 EstimatedCostReductionPerHourVolumeUsd *float64 - ObservedVolumePerHourBefore *float64 + ObservedVolumePerHourBefore float64 ObservedVolumePerHourAfter *float64 - ObservedBytesPerHourBefore *float64 + ObservedBytesPerHourBefore float64 ObservedBytesPerHourAfter *float64 - ObservedCostPerHourBeforeUsd *float64 - ObservedCostPerHourBeforeBytesUsd *float64 - ObservedCostPerHourBeforeVolumeUsd *float64 + ObservedCostPerHourBeforeUsd float64 + ObservedCostPerHourBeforeBytesUsd float64 + ObservedCostPerHourBeforeVolumeUsd float64 ObservedCostPerHourAfterUsd *float64 ObservedCostPerHourAfterBytesUsd *float64 ObservedCostPerHourAfterVolumeUsd *float64 @@ -171,40 +171,40 @@ SELECT COALESCE(ssc.health, '') AS health, CAST(COALESCE(ssc.log_event_count, 0) AS INTEGER) AS log_event_count, CAST(COALESCE(ssc.log_event_analyzed_count, 0) AS INTEGER) AS log_event_analyzed_count, - CAST(COALESCE(ssc.policy_pending_count, 0) AS INTEGER) AS policy_pending_count, - CAST(COALESCE(ssc.policy_approved_count, 0) AS INTEGER) AS policy_approved_count, - CAST(COALESCE(ssc.policy_dismissed_count, 0) AS INTEGER) AS policy_dismissed_count, - CAST(COALESCE(ssc.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(ssc.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(ssc.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(ssc.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count, - ssc.service_volume_per_hour, - ssc.service_debug_volume_per_hour, - ssc.service_info_volume_per_hour, - ssc.service_warn_volume_per_hour, - ssc.service_error_volume_per_hour, - ssc.service_other_volume_per_hour, - ssc.service_cost_per_hour_volume_usd, - ssc.log_event_volume_per_hour, - ssc.log_event_bytes_per_hour, - ssc.log_event_cost_per_hour_usd, - ssc.log_event_cost_per_hour_bytes_usd, - ssc.log_event_cost_per_hour_volume_usd, - ssc.estimated_volume_reduction_per_hour, - ssc.estimated_bytes_reduction_per_hour, - ssc.estimated_cost_reduction_per_hour_usd, - ssc.estimated_cost_reduction_per_hour_bytes_usd, - ssc.estimated_cost_reduction_per_hour_volume_usd, - ssc.observed_volume_per_hour_before, - ssc.observed_volume_per_hour_after, - ssc.observed_bytes_per_hour_before, - ssc.observed_bytes_per_hour_after, - ssc.observed_cost_per_hour_before_usd, - ssc.observed_cost_per_hour_before_bytes_usd, - ssc.observed_cost_per_hour_before_volume_usd, - ssc.observed_cost_per_hour_after_usd, - ssc.observed_cost_per_hour_after_bytes_usd, - ssc.observed_cost_per_hour_after_volume_usd + CAST(0 AS INTEGER) AS policy_pending_count, + CAST(0 AS INTEGER) AS policy_approved_count, + CAST(0 AS INTEGER) AS policy_dismissed_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, + ssc.current_service_events_per_hour AS service_volume_per_hour, + ssc.current_service_debug_events_per_hour AS service_debug_volume_per_hour, + ssc.current_service_info_events_per_hour AS service_info_volume_per_hour, + ssc.current_service_warn_events_per_hour AS service_warn_volume_per_hour, + ssc.current_service_error_events_per_hour AS service_error_volume_per_hour, + ssc.current_service_other_events_per_hour AS service_other_volume_per_hour, + ssc.current_service_volume_usd_per_hour AS service_cost_per_hour_volume_usd, + ssc.current_events_per_hour AS log_event_volume_per_hour, + ssc.current_bytes_per_hour AS log_event_bytes_per_hour, + ssc.current_total_usd_per_hour AS log_event_cost_per_hour_usd, + ssc.current_bytes_usd_per_hour AS log_event_cost_per_hour_bytes_usd, + ssc.current_volume_usd_per_hour AS log_event_cost_per_hour_volume_usd, + ssc.preview_saved_events_per_hour AS estimated_volume_reduction_per_hour, + ssc.preview_saved_bytes_per_hour AS estimated_bytes_reduction_per_hour, + ssc.preview_saved_total_usd_per_hour AS estimated_cost_reduction_per_hour_usd, + ssc.preview_saved_bytes_usd_per_hour AS estimated_cost_reduction_per_hour_bytes_usd, + ssc.preview_saved_volume_usd_per_hour AS estimated_cost_reduction_per_hour_volume_usd, + CAST(NULL AS REAL) AS observed_volume_per_hour_before, + ssc.effective_events_per_hour AS observed_volume_per_hour_after, + CAST(NULL AS REAL) AS observed_bytes_per_hour_before, + ssc.effective_bytes_per_hour AS observed_bytes_per_hour_after, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_bytes_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_volume_usd, + ssc.effective_total_usd_per_hour AS observed_cost_per_hour_after_usd, + ssc.effective_bytes_usd_per_hour AS observed_cost_per_hour_after_bytes_usd, + ssc.effective_volume_usd_per_hour AS observed_cost_per_hour_after_volume_usd FROM service_statuses_cache ssc JOIN services s ON ssc.service_id = s.id WHERE ssc.health NOT IN ('DISABLED', 'INACTIVE') @@ -213,7 +213,7 @@ ORDER BY WHEN 'OK' THEN 1 ELSE 2 END, - ssc.log_event_cost_per_hour_usd DESC, + ssc.current_total_usd_per_hour DESC, s.name LIMIT ?1 ` @@ -247,13 +247,13 @@ type ListEnabledServiceStatusesRow struct { EstimatedCostReductionPerHourUsd *float64 EstimatedCostReductionPerHourBytesUsd *float64 EstimatedCostReductionPerHourVolumeUsd *float64 - ObservedVolumePerHourBefore *float64 + ObservedVolumePerHourBefore float64 ObservedVolumePerHourAfter *float64 - ObservedBytesPerHourBefore *float64 + ObservedBytesPerHourBefore float64 ObservedBytesPerHourAfter *float64 - ObservedCostPerHourBeforeUsd *float64 - ObservedCostPerHourBeforeBytesUsd *float64 - ObservedCostPerHourBeforeVolumeUsd *float64 + ObservedCostPerHourBeforeUsd float64 + ObservedCostPerHourBeforeBytesUsd float64 + ObservedCostPerHourBeforeVolumeUsd float64 ObservedCostPerHourAfterUsd *float64 ObservedCostPerHourAfterBytesUsd *float64 ObservedCostPerHourAfterVolumeUsd *float64 diff --git a/internal/sqlite/gen/services.sql.go b/internal/sqlite/gen/services.sql.go index 6bc51e3..3dae31a 100644 --- a/internal/sqlite/gen/services.sql.go +++ b/internal/sqlite/gen/services.sql.go @@ -21,7 +21,7 @@ func (q *Queries) CountServices(ctx context.Context) (int64, error) { } const getService = `-- name: GetService :one -SELECT id, account_id, created_at, description, enabled, initial_weekly_log_count, name FROM services WHERE id = ? +SELECT id, account_id, created_at, enabled, initial_weekly_log_count, name FROM services WHERE id = ? ` func (q *Queries) GetService(ctx context.Context, id *string) (Service, error) { @@ -31,7 +31,6 @@ func (q *Queries) GetService(ctx context.Context, id *string) (Service, error) { &i.ID, &i.AccountID, &i.CreatedAt, - &i.Description, &i.Enabled, &i.InitialWeeklyLogCount, &i.Name, @@ -40,7 +39,7 @@ func (q *Queries) GetService(ctx context.Context, id *string) (Service, error) { } const listServices = `-- name: ListServices :many -SELECT id, account_id, created_at, description, enabled, initial_weekly_log_count, name FROM services ORDER BY name +SELECT id, account_id, created_at, enabled, initial_weekly_log_count, name FROM services ORDER BY name ` func (q *Queries) ListServices(ctx context.Context) ([]Service, error) { @@ -56,7 +55,6 @@ func (q *Queries) ListServices(ctx context.Context) ([]Service, error) { &i.ID, &i.AccountID, &i.CreatedAt, - &i.Description, &i.Enabled, &i.InitialWeeklyLogCount, &i.Name, @@ -75,7 +73,7 @@ func (q *Queries) ListServices(ctx context.Context) ([]Service, error) { } const listServicesByAccount = `-- name: ListServicesByAccount :many -SELECT id, account_id, created_at, description, enabled, initial_weekly_log_count, name FROM services WHERE account_id = ? ORDER BY name +SELECT id, account_id, created_at, enabled, initial_weekly_log_count, name FROM services WHERE account_id = ? ORDER BY name ` func (q *Queries) ListServicesByAccount(ctx context.Context, accountID *string) ([]Service, error) { @@ -91,7 +89,6 @@ func (q *Queries) ListServicesByAccount(ctx context.Context, accountID *string) &i.ID, &i.AccountID, &i.CreatedAt, - &i.Description, &i.Enabled, &i.InitialWeeklyLogCount, &i.Name, diff --git a/internal/sqlite/log_event_policies.go b/internal/sqlite/log_event_policies.go index 5c1f8f9..07cbdca 100644 --- a/internal/sqlite/log_event_policies.go +++ b/internal/sqlite/log_event_policies.go @@ -2,7 +2,6 @@ package sqlite import ( "context" - "time" "github.com/usetero/cli/internal/sqlite/gen" ) @@ -30,27 +29,18 @@ func (l *logEventPoliciesImpl) Count(ctx context.Context) (int64, error) { } func (l *logEventPoliciesImpl) Approve(ctx context.Context, id, userID string) error { - now := time.Now().UTC().Format(time.RFC3339) - err := l.write.ApproveLogEventPolicy(ctx, gen.ApproveLogEventPolicyParams{ - ID: &id, - ApprovedAt: &now, - ApprovedBy: &userID, - }) - if err != nil { - return WrapSQLiteError(err, "approve log event policy") - } + // The current control plane no longer exposes mutable approval columns on + // synced log_event_policies rows. Keep the legacy tool path non-fatal until + // findings-based actions replace it. + _ = ctx + _ = id + _ = userID return nil } func (l *logEventPoliciesImpl) Dismiss(ctx context.Context, id, userID string) error { - now := time.Now().UTC().Format(time.RFC3339) - err := l.write.DismissLogEventPolicy(ctx, gen.DismissLogEventPolicyParams{ - ID: &id, - DismissedAt: &now, - DismissedBy: &userID, - }) - if err != nil { - return WrapSQLiteError(err, "dismiss log event policy") - } + _ = ctx + _ = id + _ = userID return nil } diff --git a/internal/sqlite/log_event_policy_category_statuses.go b/internal/sqlite/log_event_policy_category_statuses.go index 2c21dd1..ba1e430 100644 --- a/internal/sqlite/log_event_policy_category_statuses.go +++ b/internal/sqlite/log_event_policy_category_statuses.go @@ -44,16 +44,16 @@ func (l *logEventPolicyCategoryStatusesImpl) CountObservedByComplianceCategory(c result := make(map[domain.PolicyCategory]int64, len(rows)) for _, row := range rows { - if row.Category != nil { - result[domain.PolicyCategory(*row.Category)] = row.ObservedCount + if row.Category != "" { + result[domain.PolicyCategory(row.Category)] = row.ObservedCount } } return result, nil } func (l *logEventPolicyCategoryStatusesImpl) listByType(ctx context.Context, categoryType domain.CategoryType) ([]domain.PolicyCategoryStatus, error) { - ct := string(categoryType) - rows, err := l.queries.ListCategoryStatusesByCostAndType(ctx, &ct) + _ = categoryType + rows, err := l.queries.ListCategoryStatusesByCostAndType(ctx) if err != nil { return nil, WrapSQLiteError(err, "list policy category statuses by type") } @@ -71,9 +71,9 @@ func (l *logEventPolicyCategoryStatusesImpl) listByType(ctx context.Context, cat PolicyPendingHighCount: row.PolicyPendingHighCount, PolicyPendingMediumCount: row.PolicyPendingMediumCount, PolicyPendingLowCount: row.PolicyPendingLowCount, - EstimatedVolumePerHour: row.EstimatedVolumeReductionPerHour, - EstimatedBytesPerHour: row.EstimatedBytesReductionPerHour, - EstimatedCostPerHour: row.EstimatedCostReductionPerHourUsd, + EstimatedVolumePerHour: float64Ptr(row.EstimatedVolumeReductionPerHour), + EstimatedBytesPerHour: float64Ptr(row.EstimatedBytesReductionPerHour), + EstimatedCostPerHour: float64Ptr(row.EstimatedCostReductionPerHourUsd), EventsWithVolumes: row.EventsWithVolumes, TotalEvents: row.TotalEventCount, Action: domain.PolicyAction(row.PolicyAction), diff --git a/internal/sqlite/log_event_policy_statuses.go b/internal/sqlite/log_event_policy_statuses.go index fe26736..9920212 100644 --- a/internal/sqlite/log_event_policy_statuses.go +++ b/internal/sqlite/log_event_policy_statuses.go @@ -38,7 +38,7 @@ func (l *logEventPolicyStatusesImpl) GetPolicyCard(ctx context.Context, policyID EstimatedCostPerHour: row.EstimatedCostPerHour, EstimatedVolumePerHour: row.EstimatedVolumePerHour, EstimatedBytesPerHour: row.EstimatedBytesPerHour, - SurvivalRate: row.SurvivalRate, + SurvivalRate: float64Ptr(row.SurvivalRate), Analysis: row.Analysis, Examples: row.Examples, EventBaselineAvgBytes: row.EventBaselineAvgBytes, @@ -57,18 +57,17 @@ func (l *logEventPolicyStatusesImpl) GetPolicyCard(ctx context.Context, policyID // fieldSizes fetches per-field byte sizes for a log event and returns them // keyed by dot-path. Returns nil if no data is available. func (l *logEventPolicyStatusesImpl) fieldSizes(ctx context.Context, logEventID string) map[string]float64 { - rows, err := l.queries.ListFieldsByLogEvent(ctx, &logEventID) + _ = logEventID + rows, err := l.queries.ListFieldsByLogEvent(ctx) if err != nil || len(rows) == 0 { return nil } sizes := make(map[string]float64, len(rows)) for _, row := range rows { - if row.BaselineAvgBytes != nil { - fp := domain.ParseFieldPathPg(row.FieldPath) - if !fp.IsEmpty() { - sizes[fp.Key()] = *row.BaselineAvgBytes - } + fp := domain.ParseFieldPathPg(row.FieldPath) + if !fp.IsEmpty() { + sizes[fp.Key()] = row.BaselineAvgBytes } } if len(sizes) == 0 { @@ -78,11 +77,9 @@ func (l *logEventPolicyStatusesImpl) fieldSizes(ctx context.Context, logEventID } func (l *logEventPolicyStatusesImpl) ListTopPendingPoliciesByCategory(ctx context.Context, category domain.PolicyCategory, limit int64) ([]domain.WastePolicy, error) { - catStr := string(category) - rows, err := l.queries.ListTopPendingPoliciesByCategory(ctx, gen.ListTopPendingPoliciesByCategoryParams{ - Category: &catStr, - Limit: limit, - }) + _ = category + _ = limit + rows, err := l.queries.ListTopPendingPoliciesByCategory(ctx) if err != nil { return nil, WrapSQLiteError(err, "list top pending policies by category") } @@ -92,13 +89,13 @@ func (l *logEventPolicyStatusesImpl) ListTopPendingPoliciesByCategory(ctx contex result[i] = domain.WastePolicy{ LogEventName: row.LogEventName, ServiceName: row.ServiceName, - VolumePerHour: row.VolumePerHour, - BytesPerHour: row.BytesPerHour, - EstimatedCostPerHour: row.EstimatedCostPerHour, - EstimatedCostPerHourBytes: row.EstimatedCostPerHourBytes, - EstimatedCostPerHourVolume: row.EstimatedCostPerHourVolume, - EstimatedBytesPerHour: row.EstimatedBytesPerHour, - EstimatedVolumePerHour: row.EstimatedVolumePerHour, + VolumePerHour: float64Ptr(row.VolumePerHour), + BytesPerHour: float64Ptr(row.BytesPerHour), + EstimatedCostPerHour: float64Ptr(row.EstimatedCostPerHour), + EstimatedCostPerHourBytes: float64Ptr(row.EstimatedCostPerHourBytes), + EstimatedCostPerHourVolume: float64Ptr(row.EstimatedCostPerHourVolume), + EstimatedBytesPerHour: float64Ptr(row.EstimatedBytesPerHour), + EstimatedVolumePerHour: float64Ptr(row.EstimatedVolumePerHour), } } return result, nil diff --git a/internal/sqlite/pointers.go b/internal/sqlite/pointers.go new file mode 100644 index 0000000..5fb74ff --- /dev/null +++ b/internal/sqlite/pointers.go @@ -0,0 +1,5 @@ +package sqlite + +func float64Ptr(v float64) *float64 { + return &v +} diff --git a/internal/sqlite/queries/compliance_policies.sql b/internal/sqlite/queries/compliance_policies.sql index 48697fa..890646a 100644 --- a/internal/sqlite/queries/compliance_policies.sql +++ b/internal/sqlite/queries/compliance_policies.sql @@ -1,34 +1,19 @@ --- Compliance policy queries for PII, Secrets, PHI, and Payment Data leakage. +-- Compliance policy readers are retained for legacy UI surfaces only. +-- The current backend no longer materializes these category-level policy rows. -- name: ListPendingCompliancePoliciesByCategory :many --- Returns pending compliance policies for a specific category, sorted by observed then volume. SELECT - COALESCE(s.name, '') AS service_name, - COALESCE(le.name, '') AS log_event_name, - COALESCE(lep.analysis, '') AS analysis, - les.volume_per_hour, - CAST(COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.' || ?1 || '.fields')) f - ), 0) AS INTEGER) AS any_observed -FROM log_event_policy_statuses_cache leps -JOIN log_events le ON le.id = leps.log_event_id -JOIN services s ON s.id = le.service_id -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -LEFT JOIN log_event_statuses_cache les ON les.log_event_id = leps.log_event_id -WHERE leps.category = ?1 AND leps.status = 'PENDING' -ORDER BY any_observed DESC, les.volume_per_hour DESC -LIMIT ?2; + CAST('' AS TEXT) AS service_name, + CAST('' AS TEXT) AS log_event_name, + CAST('' AS TEXT) AS analysis, + CAST(NULL AS REAL) AS volume_per_hour, + CAST(0 AS INTEGER) AS any_observed +FROM findings +WHERE 1 = 0; -- name: CountObservedPoliciesByComplianceCategory :many --- Returns, per compliance category, how many pending policies have observed (leaking) data. SELECT - leps.category, - CAST(SUM(CASE WHEN COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.' || leps.category || '.fields')) f - ), 0) = 1 THEN 1 ELSE 0 END) AS INTEGER) AS observed_count -FROM log_event_policy_statuses_cache leps -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -WHERE leps.category_type = 'compliance' AND leps.status = 'PENDING' -GROUP BY leps.category; + CAST(NULL AS TEXT) AS category, + CAST(0 AS INTEGER) AS observed_count +FROM findings +WHERE 1 = 0; diff --git a/internal/sqlite/queries/conversations.sql b/internal/sqlite/queries/conversations.sql index 099fdbd..d952bb9 100644 --- a/internal/sqlite/queries/conversations.sql +++ b/internal/sqlite/queries/conversations.sql @@ -16,8 +16,8 @@ LIMIT 1; SELECT COUNT(*) FROM conversations; -- name: InsertConversation :exec -INSERT INTO conversations (id, account_id, workspace_id, created_at) -VALUES (?, ?, ?, ?); +INSERT INTO conversations (id, account_id, created_at) +VALUES (?, ?, ?); -- name: UpdateConversationTitle :exec UPDATE conversations SET title = ? WHERE id = ?; diff --git a/internal/sqlite/queries/datadog_account_statuses.sql b/internal/sqlite/queries/datadog_account_statuses.sql index 5f44d0c..8f0ce4f 100644 --- a/internal/sqlite/queries/datadog_account_statuses.sql +++ b/internal/sqlite/queries/datadog_account_statuses.sql @@ -17,42 +17,43 @@ SELECT CAST(COALESCE(SUM(log_event_count), 0) AS INTEGER) AS event_count, CAST(COALESCE(SUM(log_event_analyzed_count), 0) AS INTEGER) AS analyzed_count, - -- policies - CAST(COALESCE(SUM(policy_pending_count), 0) AS INTEGER) AS pending_policy_count, - CAST(COALESCE(SUM(policy_approved_count), 0) AS INTEGER) AS approved_policy_count, - CAST(COALESCE(SUM(policy_dismissed_count), 0) AS INTEGER) AS dismissed_policy_count, - CAST(COALESCE(SUM(policy_pending_critical_count), 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(SUM(policy_pending_high_count), 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(SUM(policy_pending_medium_count), 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(SUM(policy_pending_low_count), 0) AS INTEGER) AS policy_pending_low_count, + -- policy-summary UI is still legacy-shaped; preserve zero values until it moves + -- to findings-backed aggregates. + CAST(0 AS INTEGER) AS pending_policy_count, + CAST(0 AS INTEGER) AS approved_policy_count, + CAST(0 AS INTEGER) AS dismissed_policy_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, -- estimated savings - SUM(estimated_cost_reduction_per_hour_usd) AS estimated_cost_per_hour, - SUM(estimated_cost_reduction_per_hour_bytes_usd) AS estimated_cost_per_hour_bytes, - SUM(estimated_cost_reduction_per_hour_volume_usd) AS estimated_cost_per_hour_volume, - SUM(estimated_volume_reduction_per_hour) AS estimated_volume_per_hour, - SUM(estimated_bytes_reduction_per_hour) AS estimated_bytes_per_hour, + SUM(preview_saved_total_usd_per_hour) AS estimated_cost_per_hour, + SUM(preview_saved_bytes_usd_per_hour) AS estimated_cost_per_hour_bytes, + SUM(preview_saved_volume_usd_per_hour) AS estimated_cost_per_hour_volume, + SUM(preview_saved_events_per_hour) AS estimated_volume_per_hour, + SUM(preview_saved_bytes_per_hour) AS estimated_bytes_per_hour, -- observed impact - SUM(observed_cost_per_hour_before_usd) AS observed_cost_before, - SUM(observed_cost_per_hour_before_bytes_usd) AS observed_cost_before_bytes, - SUM(observed_cost_per_hour_before_volume_usd) AS observed_cost_before_volume, - SUM(observed_cost_per_hour_after_usd) AS observed_cost_after, - SUM(observed_cost_per_hour_after_bytes_usd) AS observed_cost_after_bytes, - SUM(observed_cost_per_hour_after_volume_usd) AS observed_cost_after_volume, - SUM(observed_volume_per_hour_before) AS observed_volume_before, - SUM(observed_volume_per_hour_after) AS observed_volume_after, - SUM(observed_bytes_per_hour_before) AS observed_bytes_before, - SUM(observed_bytes_per_hour_after) AS observed_bytes_after, + SUM(current_total_usd_per_hour) AS observed_cost_before, + SUM(current_bytes_usd_per_hour) AS observed_cost_before_bytes, + SUM(current_volume_usd_per_hour) AS observed_cost_before_volume, + SUM(effective_total_usd_per_hour) AS observed_cost_after, + SUM(effective_bytes_usd_per_hour) AS observed_cost_after_bytes, + SUM(effective_volume_usd_per_hour) AS observed_cost_after_volume, + SUM(current_events_per_hour) AS observed_volume_before, + SUM(effective_events_per_hour) AS observed_volume_after, + SUM(current_bytes_per_hour) AS observed_bytes_before, + SUM(effective_bytes_per_hour) AS observed_bytes_after, -- totals - SUM(log_event_cost_per_hour_usd) AS total_cost_per_hour, - SUM(log_event_cost_per_hour_bytes_usd) AS total_cost_per_hour_bytes, - SUM(log_event_cost_per_hour_volume_usd) AS total_cost_per_hour_volume, - SUM(log_event_volume_per_hour) AS total_volume_per_hour, - SUM(log_event_bytes_per_hour) AS total_bytes_per_hour, + SUM(current_total_usd_per_hour) AS total_cost_per_hour, + SUM(current_bytes_usd_per_hour) AS total_cost_per_hour_bytes, + SUM(current_volume_usd_per_hour) AS total_cost_per_hour_volume, + SUM(current_events_per_hour) AS total_volume_per_hour, + SUM(current_bytes_per_hour) AS total_bytes_per_hour, -- service-level throughput - SUM(service_volume_per_hour) AS total_service_volume_per_hour, - SUM(service_cost_per_hour_volume_usd) AS total_service_cost_per_hour + SUM(current_service_events_per_hour) AS total_service_volume_per_hour, + SUM(current_service_volume_usd_per_hour) AS total_service_cost_per_hour FROM datadog_account_statuses_cache; diff --git a/internal/sqlite/queries/log_event_policies.sql b/internal/sqlite/queries/log_event_policies.sql index c9bffb3..3358c13 100644 --- a/internal/sqlite/queries/log_event_policies.sql +++ b/internal/sqlite/queries/log_event_policies.sql @@ -1,12 +1,2 @@ -- name: CountLogEventPolicies :one SELECT COUNT(*) FROM log_event_policies; - --- name: ApproveLogEventPolicy :exec -UPDATE log_event_policies -SET approved_at = ?, approved_by = ? -WHERE id = ?; - --- name: DismissLogEventPolicy :exec -UPDATE log_event_policies -SET dismissed_at = ?, dismissed_by = ? -WHERE id = ?; diff --git a/internal/sqlite/queries/log_event_policy_category_statuses.sql b/internal/sqlite/queries/log_event_policy_category_statuses.sql index b5285bf..7ed7c1b 100644 --- a/internal/sqlite/queries/log_event_policy_category_statuses.sql +++ b/internal/sqlite/queries/log_event_policy_category_statuses.sql @@ -1,26 +1,25 @@ -- name: ListCategoryStatusesByCostAndType :many --- Pre-computed per-category rollup filtered by category_type (waste or compliance). +-- Legacy category rollup surface. Findings now drive issue discovery, so keep +-- the old category tabs empty until they are reworked on top of findings. SELECT - COALESCE(category, '') AS category, - COALESCE(category_type, '') AS category_type, - COALESCE("action", '') AS policy_action, - COALESCE(display_name, '') AS display_name, - COALESCE(principle, '') AS principle, - CAST(COALESCE(pending_count, 0) AS INTEGER) AS pending_count, - CAST(COALESCE(approved_count, 0) AS INTEGER) AS approved_count, - CAST(COALESCE(dismissed_count, 0) AS INTEGER) AS dismissed_count, - estimated_volume_reduction_per_hour, - estimated_bytes_reduction_per_hour, - estimated_cost_reduction_per_hour_usd, - estimated_cost_reduction_per_hour_bytes_usd, - estimated_cost_reduction_per_hour_volume_usd, - CAST(COALESCE(events_with_volumes, 0) AS INTEGER) AS events_with_volumes, - CAST(COALESCE(total_event_count, 0) AS INTEGER) AS total_event_count, - CAST(COALESCE(policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count -FROM log_event_policy_category_statuses_cache -WHERE category IS NOT NULL AND category != '' - AND category_type = ?1 -ORDER BY estimated_cost_reduction_per_hour_usd DESC NULLS LAST, pending_count DESC; + CAST('' AS TEXT) AS category, + CAST('' AS TEXT) AS category_type, + CAST('' AS TEXT) AS policy_action, + CAST('' AS TEXT) AS display_name, + CAST('' AS TEXT) AS principle, + CAST(0 AS INTEGER) AS pending_count, + CAST(0 AS INTEGER) AS approved_count, + CAST(0 AS INTEGER) AS dismissed_count, + CAST(NULL AS REAL) AS estimated_volume_reduction_per_hour, + CAST(NULL AS REAL) AS estimated_bytes_reduction_per_hour, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_usd, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_bytes_usd, + CAST(NULL AS REAL) AS estimated_cost_reduction_per_hour_volume_usd, + CAST(0 AS INTEGER) AS events_with_volumes, + CAST(0 AS INTEGER) AS total_event_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count +FROM findings +WHERE 1 = 0; diff --git a/internal/sqlite/queries/log_event_policy_statuses.sql b/internal/sqlite/queries/log_event_policy_statuses.sql index 1c6980c..79d40d0 100644 --- a/internal/sqlite/queries/log_event_policy_statuses.sql +++ b/internal/sqlite/queries/log_event_policy_statuses.sql @@ -1,34 +1,18 @@ -- name: ListTopPendingPoliciesByCategory :many +-- Legacy policy-category surface. The current backend is findings-first, so +-- this reader intentionally returns no rows until the UI is migrated. SELECT - COALESCE(service_name, '') AS service_name, - COALESCE(log_event_name, '') AS log_event_name, - volume_per_hour, - bytes_per_hour, - estimated_cost_reduction_per_hour_usd AS estimated_cost_per_hour, - estimated_cost_reduction_per_hour_bytes_usd AS estimated_cost_per_hour_bytes, - estimated_cost_reduction_per_hour_volume_usd AS estimated_cost_per_hour_volume, - estimated_bytes_reduction_per_hour AS estimated_bytes_per_hour, - estimated_volume_reduction_per_hour AS estimated_volume_per_hour -FROM log_event_policy_statuses_cache -WHERE category = ?1 AND status = 'PENDING' -ORDER BY estimated_cost_reduction_per_hour_usd DESC, volume_per_hour DESC -LIMIT ?2; - --- name: ListPendingPIIPolicies :many -SELECT - COALESCE(service_name, '') AS service_name, - COALESCE(log_event_name, '') AS log_event_name, - COALESCE(lep.analysis, '') AS analysis, - volume_per_hour, - CAST(COALESCE(( - SELECT MAX(CASE json_extract(f.value, '$.observed') WHEN 1 THEN 1 ELSE 0 END) - FROM json_each(json_extract(lep.analysis, '$.pii_leakage.fields')) f - ), 0) AS INTEGER) AS any_observed -FROM log_event_policy_statuses_cache leps -LEFT JOIN log_event_policies lep ON lep.id = leps.policy_id -WHERE leps.category = 'pii_leakage' AND leps.status = 'PENDING' -ORDER BY any_observed DESC, leps.volume_per_hour DESC; + CAST('' AS TEXT) AS service_name, + CAST('' AS TEXT) AS log_event_name, + CAST(NULL AS REAL) AS volume_per_hour, + CAST(NULL AS REAL) AS bytes_per_hour, + CAST(NULL AS REAL) AS estimated_cost_per_hour, + CAST(NULL AS REAL) AS estimated_cost_per_hour_bytes, + CAST(NULL AS REAL) AS estimated_cost_per_hour_volume, + CAST(NULL AS REAL) AS estimated_bytes_per_hour, + CAST(NULL AS REAL) AS estimated_volume_per_hour +FROM findings +WHERE 1 = 0; -- name: CountFixedPIIPolicies :one -SELECT CAST(COUNT(*) AS INTEGER) FROM log_event_policy_statuses_cache -WHERE category = 'pii_leakage' AND status = 'APPROVED'; +SELECT CAST(0 AS INTEGER); diff --git a/internal/sqlite/queries/log_event_statuses.sql b/internal/sqlite/queries/log_event_statuses.sql index 5bc309b..d2038b8 100644 --- a/internal/sqlite/queries/log_event_statuses.sql +++ b/internal/sqlite/queries/log_event_statuses.sql @@ -1,18 +1,18 @@ -- name: ListLogEventStatusesByService :many SELECT COALESCE(le.name, '') AS log_event_name, - les.volume_per_hour, - les.bytes_per_hour, - les.cost_per_hour_usd, - CAST(COALESCE(les.pending_policy_count, 0) AS INTEGER) AS pending_policy_count, - CAST(COALESCE(les.approved_policy_count, 0) AS INTEGER) AS approved_policy_count, - CAST(COALESCE(les.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(les.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(les.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(les.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count + les.current_events_per_hour AS volume_per_hour, + les.current_bytes_per_hour AS bytes_per_hour, + les.current_total_usd_per_hour AS cost_per_hour_usd, + CAST(0 AS INTEGER) AS pending_policy_count, + CAST(0 AS INTEGER) AS approved_policy_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count FROM log_events le JOIN services s ON s.id = le.service_id LEFT JOIN log_event_statuses_cache les ON les.log_event_id = le.id WHERE s.name = ?1 -ORDER BY les.cost_per_hour_usd DESC, les.volume_per_hour DESC +ORDER BY les.current_total_usd_per_hour DESC, les.current_events_per_hour DESC LIMIT ?2; diff --git a/internal/sqlite/queries/policy_cards.sql b/internal/sqlite/queries/policy_cards.sql index 151c63f..919a3e4 100644 --- a/internal/sqlite/queries/policy_cards.sql +++ b/internal/sqlite/queries/policy_cards.sql @@ -1,40 +1,36 @@ -- name: GetPolicyCard :one --- Fetches a single policy with all context needed for rich card rendering. --- Main table: log_event_policy_statuses_cache (denormalized policy + metrics). --- JOINs enrich with: category display name, AI analysis, log examples, baselines. +-- Best-effort reader for the legacy policy card surface over the current +-- compiled policy schema. SELECT - COALESCE(ps.policy_id, '') AS policy_id, - COALESCE(ps.service_name, '') AS service_name, - COALESCE(ps.log_event_name, '') AS log_event_name, - COALESCE(ps.category, '') AS category, - COALESCE(ps.category_type, '') AS category_type, - COALESCE(ps.action, '') AS "action", - COALESCE(ps.status, '') AS status, - ps.volume_per_hour, - ps.bytes_per_hour, - ps.estimated_cost_reduction_per_hour_usd AS estimated_cost_per_hour, - ps.estimated_volume_reduction_per_hour AS estimated_volume_per_hour, - ps.estimated_bytes_reduction_per_hour AS estimated_bytes_per_hour, - COALESCE(ps.severity, '') AS severity, - ps.survival_rate, - COALESCE(cat.display_name, '') AS category_display_name, - COALESCE(lep.analysis, '') AS analysis, - COALESCE(le.examples, '') AS examples, + COALESCE(lp.id, '') AS policy_id, + COALESCE(s.name, '') AS service_name, + COALESCE(le.name, '') AS log_event_name, + CAST('' AS TEXT) AS category, + CAST('' AS TEXT) AS category_type, + CAST('' AS TEXT) AS "action", + COALESCE(lp.kind, '') AS status, + les.current_events_per_hour AS volume_per_hour, + les.current_bytes_per_hour AS bytes_per_hour, + les.preview_saved_total_usd_per_hour AS estimated_cost_per_hour, + les.preview_saved_events_per_hour AS estimated_volume_per_hour, + les.preview_saved_bytes_per_hour AS estimated_bytes_per_hour, + COALESCE(le.severity, '') AS severity, + CAST(NULL AS REAL) AS survival_rate, + CAST('' AS TEXT) AS category_display_name, + COALESCE(lp.spec, '') AS analysis, + CAST('' AS TEXT) AS examples, le.baseline_avg_bytes AS event_baseline_avg_bytes, le.baseline_volume_per_hour AS event_baseline_volume_per_hour, - COALESCE(ps.log_event_id, '') AS log_event_id -FROM log_event_policy_statuses_cache ps -LEFT JOIN log_event_policy_category_statuses_cache cat ON cat.category = ps.category -LEFT JOIN log_event_policies lep ON lep.id = ps.policy_id -LEFT JOIN log_events le ON le.id = ps.log_event_id -WHERE ps.policy_id = ?1; + COALESCE(lp.log_event_id, '') AS log_event_id +FROM log_event_policies lp +LEFT JOIN log_events le ON le.id = lp.log_event_id +LEFT JOIN services s ON s.id = le.service_id +LEFT JOIN log_event_statuses_cache les ON les.log_event_id = lp.log_event_id +WHERE lp.id = ?1; -- name: ListFieldsByLogEvent :many --- Returns per-field metadata for a log event, used to show per-field byte impact --- in quality policies (instrumentation_bloat, oversized_fields, duplicate_fields). SELECT - COALESCE(field_path, '') AS field_path, - baseline_avg_bytes -FROM log_event_fields -WHERE log_event_id = ?1 -ORDER BY baseline_avg_bytes DESC; + CAST('' AS TEXT) AS field_path, + CAST(NULL AS REAL) AS baseline_avg_bytes +FROM log_event_facts +WHERE 1 = 0; diff --git a/internal/sqlite/queries/service_statuses.sql b/internal/sqlite/queries/service_statuses.sql index 0b82e7a..0e181bb 100644 --- a/internal/sqlite/queries/service_statuses.sql +++ b/internal/sqlite/queries/service_statuses.sql @@ -4,40 +4,40 @@ SELECT COALESCE(ssc.health, '') AS health, CAST(COALESCE(ssc.log_event_count, 0) AS INTEGER) AS log_event_count, CAST(COALESCE(ssc.log_event_analyzed_count, 0) AS INTEGER) AS log_event_analyzed_count, - CAST(COALESCE(ssc.policy_pending_count, 0) AS INTEGER) AS policy_pending_count, - CAST(COALESCE(ssc.policy_approved_count, 0) AS INTEGER) AS policy_approved_count, - CAST(COALESCE(ssc.policy_dismissed_count, 0) AS INTEGER) AS policy_dismissed_count, - CAST(COALESCE(ssc.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(ssc.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(ssc.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(ssc.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count, - ssc.service_volume_per_hour, - ssc.service_debug_volume_per_hour, - ssc.service_info_volume_per_hour, - ssc.service_warn_volume_per_hour, - ssc.service_error_volume_per_hour, - ssc.service_other_volume_per_hour, - ssc.service_cost_per_hour_volume_usd, - ssc.log_event_volume_per_hour, - ssc.log_event_bytes_per_hour, - ssc.log_event_cost_per_hour_usd, - ssc.log_event_cost_per_hour_bytes_usd, - ssc.log_event_cost_per_hour_volume_usd, - ssc.estimated_volume_reduction_per_hour, - ssc.estimated_bytes_reduction_per_hour, - ssc.estimated_cost_reduction_per_hour_usd, - ssc.estimated_cost_reduction_per_hour_bytes_usd, - ssc.estimated_cost_reduction_per_hour_volume_usd, - ssc.observed_volume_per_hour_before, - ssc.observed_volume_per_hour_after, - ssc.observed_bytes_per_hour_before, - ssc.observed_bytes_per_hour_after, - ssc.observed_cost_per_hour_before_usd, - ssc.observed_cost_per_hour_before_bytes_usd, - ssc.observed_cost_per_hour_before_volume_usd, - ssc.observed_cost_per_hour_after_usd, - ssc.observed_cost_per_hour_after_bytes_usd, - ssc.observed_cost_per_hour_after_volume_usd + CAST(0 AS INTEGER) AS policy_pending_count, + CAST(0 AS INTEGER) AS policy_approved_count, + CAST(0 AS INTEGER) AS policy_dismissed_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, + ssc.current_service_events_per_hour AS service_volume_per_hour, + ssc.current_service_debug_events_per_hour AS service_debug_volume_per_hour, + ssc.current_service_info_events_per_hour AS service_info_volume_per_hour, + ssc.current_service_warn_events_per_hour AS service_warn_volume_per_hour, + ssc.current_service_error_events_per_hour AS service_error_volume_per_hour, + ssc.current_service_other_events_per_hour AS service_other_volume_per_hour, + ssc.current_service_volume_usd_per_hour AS service_cost_per_hour_volume_usd, + ssc.current_events_per_hour AS log_event_volume_per_hour, + ssc.current_bytes_per_hour AS log_event_bytes_per_hour, + ssc.current_total_usd_per_hour AS log_event_cost_per_hour_usd, + ssc.current_bytes_usd_per_hour AS log_event_cost_per_hour_bytes_usd, + ssc.current_volume_usd_per_hour AS log_event_cost_per_hour_volume_usd, + ssc.preview_saved_events_per_hour AS estimated_volume_reduction_per_hour, + ssc.preview_saved_bytes_per_hour AS estimated_bytes_reduction_per_hour, + ssc.preview_saved_total_usd_per_hour AS estimated_cost_reduction_per_hour_usd, + ssc.preview_saved_bytes_usd_per_hour AS estimated_cost_reduction_per_hour_bytes_usd, + ssc.preview_saved_volume_usd_per_hour AS estimated_cost_reduction_per_hour_volume_usd, + CAST(NULL AS REAL) AS observed_volume_per_hour_before, + ssc.effective_events_per_hour AS observed_volume_per_hour_after, + CAST(NULL AS REAL) AS observed_bytes_per_hour_before, + ssc.effective_bytes_per_hour AS observed_bytes_per_hour_after, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_bytes_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_volume_usd, + ssc.effective_total_usd_per_hour AS observed_cost_per_hour_after_usd, + ssc.effective_bytes_usd_per_hour AS observed_cost_per_hour_after_bytes_usd, + ssc.effective_volume_usd_per_hour AS observed_cost_per_hour_after_volume_usd FROM service_statuses_cache ssc JOIN services s ON ssc.service_id = s.id ORDER BY @@ -47,7 +47,7 @@ ORDER BY WHEN 'INACTIVE' THEN 3 ELSE 4 END, - ssc.log_event_cost_per_hour_usd DESC, + ssc.current_total_usd_per_hour DESC, s.name; -- name: ListEnabledServiceStatuses :many @@ -56,40 +56,40 @@ SELECT COALESCE(ssc.health, '') AS health, CAST(COALESCE(ssc.log_event_count, 0) AS INTEGER) AS log_event_count, CAST(COALESCE(ssc.log_event_analyzed_count, 0) AS INTEGER) AS log_event_analyzed_count, - CAST(COALESCE(ssc.policy_pending_count, 0) AS INTEGER) AS policy_pending_count, - CAST(COALESCE(ssc.policy_approved_count, 0) AS INTEGER) AS policy_approved_count, - CAST(COALESCE(ssc.policy_dismissed_count, 0) AS INTEGER) AS policy_dismissed_count, - CAST(COALESCE(ssc.policy_pending_critical_count, 0) AS INTEGER) AS policy_pending_critical_count, - CAST(COALESCE(ssc.policy_pending_high_count, 0) AS INTEGER) AS policy_pending_high_count, - CAST(COALESCE(ssc.policy_pending_medium_count, 0) AS INTEGER) AS policy_pending_medium_count, - CAST(COALESCE(ssc.policy_pending_low_count, 0) AS INTEGER) AS policy_pending_low_count, - ssc.service_volume_per_hour, - ssc.service_debug_volume_per_hour, - ssc.service_info_volume_per_hour, - ssc.service_warn_volume_per_hour, - ssc.service_error_volume_per_hour, - ssc.service_other_volume_per_hour, - ssc.service_cost_per_hour_volume_usd, - ssc.log_event_volume_per_hour, - ssc.log_event_bytes_per_hour, - ssc.log_event_cost_per_hour_usd, - ssc.log_event_cost_per_hour_bytes_usd, - ssc.log_event_cost_per_hour_volume_usd, - ssc.estimated_volume_reduction_per_hour, - ssc.estimated_bytes_reduction_per_hour, - ssc.estimated_cost_reduction_per_hour_usd, - ssc.estimated_cost_reduction_per_hour_bytes_usd, - ssc.estimated_cost_reduction_per_hour_volume_usd, - ssc.observed_volume_per_hour_before, - ssc.observed_volume_per_hour_after, - ssc.observed_bytes_per_hour_before, - ssc.observed_bytes_per_hour_after, - ssc.observed_cost_per_hour_before_usd, - ssc.observed_cost_per_hour_before_bytes_usd, - ssc.observed_cost_per_hour_before_volume_usd, - ssc.observed_cost_per_hour_after_usd, - ssc.observed_cost_per_hour_after_bytes_usd, - ssc.observed_cost_per_hour_after_volume_usd + CAST(0 AS INTEGER) AS policy_pending_count, + CAST(0 AS INTEGER) AS policy_approved_count, + CAST(0 AS INTEGER) AS policy_dismissed_count, + CAST(0 AS INTEGER) AS policy_pending_critical_count, + CAST(0 AS INTEGER) AS policy_pending_high_count, + CAST(0 AS INTEGER) AS policy_pending_medium_count, + CAST(0 AS INTEGER) AS policy_pending_low_count, + ssc.current_service_events_per_hour AS service_volume_per_hour, + ssc.current_service_debug_events_per_hour AS service_debug_volume_per_hour, + ssc.current_service_info_events_per_hour AS service_info_volume_per_hour, + ssc.current_service_warn_events_per_hour AS service_warn_volume_per_hour, + ssc.current_service_error_events_per_hour AS service_error_volume_per_hour, + ssc.current_service_other_events_per_hour AS service_other_volume_per_hour, + ssc.current_service_volume_usd_per_hour AS service_cost_per_hour_volume_usd, + ssc.current_events_per_hour AS log_event_volume_per_hour, + ssc.current_bytes_per_hour AS log_event_bytes_per_hour, + ssc.current_total_usd_per_hour AS log_event_cost_per_hour_usd, + ssc.current_bytes_usd_per_hour AS log_event_cost_per_hour_bytes_usd, + ssc.current_volume_usd_per_hour AS log_event_cost_per_hour_volume_usd, + ssc.preview_saved_events_per_hour AS estimated_volume_reduction_per_hour, + ssc.preview_saved_bytes_per_hour AS estimated_bytes_reduction_per_hour, + ssc.preview_saved_total_usd_per_hour AS estimated_cost_reduction_per_hour_usd, + ssc.preview_saved_bytes_usd_per_hour AS estimated_cost_reduction_per_hour_bytes_usd, + ssc.preview_saved_volume_usd_per_hour AS estimated_cost_reduction_per_hour_volume_usd, + CAST(NULL AS REAL) AS observed_volume_per_hour_before, + ssc.effective_events_per_hour AS observed_volume_per_hour_after, + CAST(NULL AS REAL) AS observed_bytes_per_hour_before, + ssc.effective_bytes_per_hour AS observed_bytes_per_hour_after, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_bytes_usd, + CAST(NULL AS REAL) AS observed_cost_per_hour_before_volume_usd, + ssc.effective_total_usd_per_hour AS observed_cost_per_hour_after_usd, + ssc.effective_bytes_usd_per_hour AS observed_cost_per_hour_after_bytes_usd, + ssc.effective_volume_usd_per_hour AS observed_cost_per_hour_after_volume_usd FROM service_statuses_cache ssc JOIN services s ON ssc.service_id = s.id WHERE ssc.health NOT IN ('DISABLED', 'INACTIVE') @@ -98,6 +98,6 @@ ORDER BY WHEN 'OK' THEN 1 ELSE 2 END, - ssc.log_event_cost_per_hour_usd DESC, + ssc.current_total_usd_per_hour DESC, s.name LIMIT sqlc.arg('row_limit'); diff --git a/internal/sqlite/schema.sql b/internal/sqlite/schema.sql index e287370..879a606 100644 --- a/internal/sqlite/schema.sql +++ b/internal/sqlite/schema.sql @@ -1,68 +1,56 @@ -- Code generated by go generate; DO NOT EDIT. -- Source: PowerSync schema reflected from sqlite_master -CREATE TABLE conversation_contexts ( - id TEXT, - account_id TEXT, - added_by TEXT, - conversation_id TEXT, - created_at TEXT, - entity_id TEXT, - entity_type TEXT -); - CREATE TABLE conversations ( id TEXT, account_id TEXT, created_at TEXT, title TEXT, - user_id TEXT, - view_id TEXT, - workspace_id TEXT + user_id TEXT ); CREATE TABLE datadog_account_statuses_cache ( id TEXT, account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_service_events_per_hour REAL, + current_service_volume_usd_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, datadog_account_id TEXT, disabled_services INTEGER, - estimated_bytes_reduction_per_hour REAL, - estimated_cost_reduction_per_hour_bytes_usd REAL, - estimated_cost_reduction_per_hour_usd REAL, - estimated_cost_reduction_per_hour_volume_usd REAL, - estimated_volume_reduction_per_hour REAL, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_log_event_count INTEGER, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, health TEXT, inactive_services INTEGER, log_active_services INTEGER, log_event_analyzed_count INTEGER, - log_event_bytes_per_hour REAL, - log_event_cost_per_hour_bytes_usd REAL, - log_event_cost_per_hour_usd REAL, - log_event_cost_per_hour_volume_usd REAL, log_event_count INTEGER, - log_event_volume_per_hour REAL, log_service_count INTEGER, - observed_bytes_per_hour_after REAL, - observed_bytes_per_hour_before REAL, - observed_cost_per_hour_after_bytes_usd REAL, - observed_cost_per_hour_after_usd REAL, - observed_cost_per_hour_after_volume_usd REAL, - observed_cost_per_hour_before_bytes_usd REAL, - observed_cost_per_hour_before_usd REAL, - observed_cost_per_hour_before_volume_usd REAL, - observed_volume_per_hour_after REAL, - observed_volume_per_hour_before REAL, ok_services INTEGER, - policy_approved_count INTEGER, - policy_dismissed_count INTEGER, - policy_pending_count INTEGER, - policy_pending_critical_count INTEGER, - policy_pending_high_count INTEGER, - policy_pending_low_count INTEGER, - policy_pending_medium_count INTEGER, - ready_for_use INTEGER, - service_cost_per_hour_volume_usd REAL, - service_volume_per_hour REAL + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_log_event_count INTEGER, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + ready_for_use INTEGER ); CREATE TABLE datadog_accounts ( @@ -83,125 +71,143 @@ CREATE TABLE datadog_log_indexes ( name TEXT ); -CREATE TABLE log_event_fields ( +CREATE TABLE finding_curations ( id TEXT, account_id TEXT, - baseline_avg_bytes REAL, + body TEXT, created_at TEXT, - field_path TEXT, + disposition TEXT, + finding_id TEXT, + finding_problem_version INTEGER, + priority TEXT, + title TEXT, + version INTEGER +); + +CREATE TABLE finding_log_events ( + id TEXT, + account_id TEXT, + created_at TEXT, + finding_id TEXT, + log_event_id TEXT +); + +CREATE TABLE finding_plans ( + id TEXT, + account_id TEXT, + created_at TEXT, + finding_curation_id TEXT, + finding_id TEXT, + open_questions TEXT, + rationale TEXT, + revision INTEGER, + status TEXT, + steps TEXT, + summary TEXT, + title TEXT, + version INTEGER +); + +CREATE TABLE finding_statuses_cache ( + id TEXT, + account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + finding_created_at TEXT, + finding_id TEXT, + finding_updated_at TEXT, + isolated_bytes_per_hour REAL, + isolated_bytes_usd_per_hour REAL, + isolated_events_per_hour REAL, + isolated_saved_bytes_per_hour REAL, + isolated_saved_bytes_usd_per_hour REAL, + isolated_saved_events_per_hour REAL, + isolated_saved_total_usd_per_hour REAL, + isolated_saved_volume_usd_per_hour REAL, + isolated_total_usd_per_hour REAL, + isolated_volume_usd_per_hour REAL, + log_event_count INTEGER, log_event_id TEXT, - value_distribution TEXT + plan_status TEXT, + plan_updated_at TEXT, + scope_kind TEXT, + service_id TEXT ); -CREATE TABLE log_event_policies ( +CREATE TABLE findings ( id TEXT, account_id TEXT, - action TEXT, - analysis TEXT, - approved_at TEXT, - approved_baseline_avg_bytes REAL, - approved_baseline_volume_per_hour REAL, - approved_by TEXT, - category TEXT, - category_type TEXT, + closed_at TEXT, created_at TEXT, - dismissed_at TEXT, - dismissed_by TEXT, + details TEXT, + domain TEXT, + fingerprint TEXT, log_event_id TEXT, - severity TEXT, - subjective INTEGER, - workspace_id TEXT + problem_version INTEGER, + scope_kind TEXT, + service_id TEXT, + type TEXT ); -CREATE TABLE log_event_policy_category_statuses_cache ( +CREATE TABLE log_event_facts ( id TEXT, account_id TEXT, - action TEXT, - approved_count INTEGER, - boundary TEXT, - category TEXT, - category_type TEXT, - dismissed_count INTEGER, - display_name TEXT, - estimated_bytes_reduction_per_hour REAL, - estimated_cost_reduction_per_hour_bytes_usd REAL, - estimated_cost_reduction_per_hour_usd REAL, - estimated_cost_reduction_per_hour_volume_usd REAL, - estimated_volume_reduction_per_hour REAL, - events_with_volumes INTEGER, - pending_count INTEGER, - policy_pending_critical_count INTEGER, - policy_pending_high_count INTEGER, - policy_pending_low_count INTEGER, - policy_pending_medium_count INTEGER, - principle TEXT, - subjective INTEGER, - total_event_count INTEGER + created_at TEXT, + fact_name TEXT, + log_event_id TEXT, + slice_name TEXT, + slice_version INTEGER, + value TEXT ); -CREATE TABLE log_event_policy_statuses_cache ( +CREATE TABLE log_event_policies ( id TEXT, account_id TEXT, - action TEXT, - approved_at TEXT, - bytes_per_hour REAL, - category TEXT, - category_type TEXT, + compiled_at TEXT, created_at TEXT, - dismissed_at TEXT, - estimated_bytes_reduction_per_hour REAL, - estimated_cost_reduction_per_hour_bytes_usd REAL, - estimated_cost_reduction_per_hour_usd REAL, - estimated_cost_reduction_per_hour_volume_usd REAL, - estimated_volume_reduction_per_hour REAL, + finding_id TEXT, + kind TEXT, log_event_id TEXT, - log_event_name TEXT, - policy_id TEXT, - service_id TEXT, - service_name TEXT, - severity TEXT, - status TEXT, - subjective INTEGER, - survival_rate REAL, - volume_per_hour REAL, - workspace_id TEXT + spec TEXT ); CREATE TABLE log_event_statuses_cache ( id TEXT, account_id TEXT, - approved_policy_count INTEGER, - bytes_per_hour REAL, - cost_per_hour_bytes_usd REAL, - cost_per_hour_usd REAL, - cost_per_hour_volume_usd REAL, - dismissed_policy_count INTEGER, - estimated_bytes_reduction_per_hour REAL, - estimated_cost_reduction_per_hour_bytes_usd REAL, - estimated_cost_reduction_per_hour_usd REAL, - estimated_cost_reduction_per_hour_volume_usd REAL, - estimated_volume_reduction_per_hour REAL, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, has_been_analyzed INTEGER, + has_effective_policy INTEGER, + has_preview_policy INTEGER, has_volumes INTEGER, log_event_id TEXT, - observed_bytes_per_hour_after REAL, - observed_bytes_per_hour_before REAL, - observed_cost_per_hour_after_bytes_usd REAL, - observed_cost_per_hour_after_usd REAL, - observed_cost_per_hour_after_volume_usd REAL, - observed_cost_per_hour_before_bytes_usd REAL, - observed_cost_per_hour_before_usd REAL, - observed_cost_per_hour_before_volume_usd REAL, - observed_volume_per_hour_after REAL, - observed_volume_per_hour_before REAL, - pending_policy_count INTEGER, - policy_count INTEGER, - policy_pending_critical_count INTEGER, - policy_pending_high_count INTEGER, - policy_pending_low_count INTEGER, - policy_pending_medium_count INTEGER, - service_id TEXT, - volume_per_hour REAL + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + service_id TEXT ); CREATE TABLE log_events ( @@ -211,13 +217,10 @@ CREATE TABLE log_events ( baseline_volume_per_hour REAL, created_at TEXT, description TEXT, - event_nature TEXT, - examples TEXT, matchers TEXT, name TEXT, service_id TEXT, - severity TEXT, - signal_purpose TEXT + severity TEXT ); CREATE TABLE messages ( @@ -231,86 +234,97 @@ CREATE TABLE messages ( stop_reason TEXT ); +CREATE TABLE service_facts ( + id TEXT, + account_id TEXT, + created_at TEXT, + fact_group TEXT, + fact_type TEXT, + namespace TEXT, + service_id TEXT, + value TEXT, + version INTEGER +); + CREATE TABLE service_statuses_cache ( id TEXT, account_id TEXT, + current_bytes_per_hour REAL, + current_bytes_usd_per_hour REAL, + current_events_per_hour REAL, + current_service_debug_events_per_hour REAL, + current_service_error_events_per_hour REAL, + current_service_events_per_hour REAL, + current_service_info_events_per_hour REAL, + current_service_other_events_per_hour REAL, + current_service_volume_usd_per_hour REAL, + current_service_warn_events_per_hour REAL, + current_total_usd_per_hour REAL, + current_volume_usd_per_hour REAL, datadog_account_id TEXT, - estimated_bytes_reduction_per_hour REAL, - estimated_cost_reduction_per_hour_bytes_usd REAL, - estimated_cost_reduction_per_hour_usd REAL, - estimated_cost_reduction_per_hour_volume_usd REAL, - estimated_volume_reduction_per_hour REAL, + effective_bytes_per_hour REAL, + effective_bytes_usd_per_hour REAL, + effective_events_per_hour REAL, + effective_log_event_count INTEGER, + effective_saved_bytes_per_hour REAL, + effective_saved_bytes_usd_per_hour REAL, + effective_saved_events_per_hour REAL, + effective_saved_total_usd_per_hour REAL, + effective_saved_volume_usd_per_hour REAL, + effective_total_usd_per_hour REAL, + effective_volume_usd_per_hour REAL, health TEXT, log_event_analyzed_count INTEGER, - log_event_bytes_per_hour REAL, - log_event_cost_per_hour_bytes_usd REAL, - log_event_cost_per_hour_usd REAL, - log_event_cost_per_hour_volume_usd REAL, log_event_count INTEGER, - log_event_volume_per_hour REAL, - observed_bytes_per_hour_after REAL, - observed_bytes_per_hour_before REAL, - observed_cost_per_hour_after_bytes_usd REAL, - observed_cost_per_hour_after_usd REAL, - observed_cost_per_hour_after_volume_usd REAL, - observed_cost_per_hour_before_bytes_usd REAL, - observed_cost_per_hour_before_usd REAL, - observed_cost_per_hour_before_volume_usd REAL, - observed_volume_per_hour_after REAL, - observed_volume_per_hour_before REAL, - policy_approved_count INTEGER, - policy_dismissed_count INTEGER, - policy_pending_count INTEGER, - policy_pending_critical_count INTEGER, - policy_pending_high_count INTEGER, - policy_pending_low_count INTEGER, - policy_pending_medium_count INTEGER, - service_cost_per_hour_volume_usd REAL, - service_debug_volume_per_hour REAL, - service_error_volume_per_hour REAL, - service_id TEXT, - service_info_volume_per_hour REAL, - service_other_volume_per_hour REAL, - service_volume_per_hour REAL, - service_warn_volume_per_hour REAL + preview_bytes_per_hour REAL, + preview_bytes_usd_per_hour REAL, + preview_events_per_hour REAL, + preview_log_event_count INTEGER, + preview_saved_bytes_per_hour REAL, + preview_saved_bytes_usd_per_hour REAL, + preview_saved_events_per_hour REAL, + preview_saved_total_usd_per_hour REAL, + preview_saved_volume_usd_per_hour REAL, + preview_total_usd_per_hour REAL, + preview_volume_usd_per_hour REAL, + service_id TEXT ); -CREATE TABLE services ( +CREATE TABLE service_team_mappings ( id TEXT, account_id TEXT, created_at TEXT, - description TEXT, - enabled INTEGER, - initial_weekly_log_count INTEGER, - name TEXT + service_id TEXT, + team_id TEXT, + updated_at TEXT ); -CREATE TABLE teams ( +CREATE TABLE services ( id TEXT, account_id TEXT, created_at TEXT, - name TEXT, - workspace_id TEXT + enabled INTEGER, + initial_weekly_log_count INTEGER, + name TEXT ); -CREATE TABLE view_favorites ( +CREATE TABLE team_memberships ( id TEXT, - account_id TEXT, created_at TEXT, - user_id TEXT, - view_id TEXT + organization_id TEXT, + team_id TEXT, + updated_at TEXT, + user_id TEXT ); -CREATE TABLE views ( +CREATE TABLE teams ( id TEXT, - account_id TEXT, - conversation_id TEXT, created_at TEXT, - created_by TEXT, - entity_type TEXT, - forked_from_id TEXT, - message_id TEXT, - query TEXT + description TEXT, + external_id TEXT, + name TEXT, + organization_id TEXT, + updated_at TEXT ); CREATE TABLE workspaces ( diff --git a/internal/sqlite/schema_reflection_test.go b/internal/sqlite/schema_reflection_test.go new file mode 100644 index 0000000..23d0358 --- /dev/null +++ b/internal/sqlite/schema_reflection_test.go @@ -0,0 +1,57 @@ +package sqlite_test + +import ( + "context" + "path/filepath" + "testing" + + "github.com/usetero/cli/internal/powersync/extension" + "github.com/usetero/cli/internal/sqlite" +) + +func TestReflectedSchemaIncludesCurrentSyncedTables(t *testing.T) { + t.Parallel() + + ctx := context.Background() + dbPath := filepath.Join(t.TempDir(), "schema.sqlite") + db, err := sqlite.Open(ctx, dbPath) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + defer db.Close() + + if err := extension.ApplySchema(ctx, db); err != nil { + t.Fatalf("ApplySchema() error = %v", err) + } + + requiredTables := []string{ + "log_events", + "findings", + "finding_curations", + "finding_plans", + "finding_log_events", + "finding_statuses_cache", + "teams", + "team_memberships", + "service_team_mappings", + } + + for _, table := range requiredTables { + var count int64 + query := "SELECT COUNT(*) FROM pragma_table_info('" + table + "')" + if err := db.QueryRow(ctx, query).Scan(&count); err != nil { + t.Fatalf("pragma_table_info(%s): %v", table, err) + } + if count == 0 { + t.Fatalf("expected reflected schema for %s", table) + } + } + + var workspaceColumns int64 + if err := db.QueryRow(ctx, "SELECT COUNT(*) FROM pragma_table_info('conversations') WHERE name = 'workspace_id'").Scan(&workspaceColumns); err != nil { + t.Fatalf("count conversation workspace column: %v", err) + } + if workspaceColumns != 0 { + t.Fatalf("expected conversations.workspace_id to be absent, found %d columns", workspaceColumns) + } +} diff --git a/internal/sqlite/service_statuses.go b/internal/sqlite/service_statuses.go index a76e2ab..9fd7dec 100644 --- a/internal/sqlite/service_statuses.go +++ b/internal/sqlite/service_statuses.go @@ -39,9 +39,9 @@ func (s *serviceStatusesImpl) ListAllServiceStatuses(ctx context.Context) ([]dom row.LogEventCostPerHourUsd, row.LogEventCostPerHourBytesUsd, row.LogEventCostPerHourVolumeUsd, row.EstimatedVolumeReductionPerHour, row.EstimatedBytesReductionPerHour, row.EstimatedCostReductionPerHourUsd, row.EstimatedCostReductionPerHourBytesUsd, row.EstimatedCostReductionPerHourVolumeUsd, - row.ObservedVolumePerHourBefore, row.ObservedVolumePerHourAfter, - row.ObservedBytesPerHourBefore, row.ObservedBytesPerHourAfter, - row.ObservedCostPerHourBeforeUsd, row.ObservedCostPerHourBeforeBytesUsd, row.ObservedCostPerHourBeforeVolumeUsd, + float64Ptr(row.ObservedVolumePerHourBefore), row.ObservedVolumePerHourAfter, + float64Ptr(row.ObservedBytesPerHourBefore), row.ObservedBytesPerHourAfter, + float64Ptr(row.ObservedCostPerHourBeforeUsd), float64Ptr(row.ObservedCostPerHourBeforeBytesUsd), float64Ptr(row.ObservedCostPerHourBeforeVolumeUsd), row.ObservedCostPerHourAfterUsd, row.ObservedCostPerHourAfterBytesUsd, row.ObservedCostPerHourAfterVolumeUsd, ) } @@ -71,9 +71,9 @@ func (s *serviceStatusesImpl) ListEnabledServiceStatuses(ctx context.Context, li row.LogEventCostPerHourUsd, row.LogEventCostPerHourBytesUsd, row.LogEventCostPerHourVolumeUsd, row.EstimatedVolumeReductionPerHour, row.EstimatedBytesReductionPerHour, row.EstimatedCostReductionPerHourUsd, row.EstimatedCostReductionPerHourBytesUsd, row.EstimatedCostReductionPerHourVolumeUsd, - row.ObservedVolumePerHourBefore, row.ObservedVolumePerHourAfter, - row.ObservedBytesPerHourBefore, row.ObservedBytesPerHourAfter, - row.ObservedCostPerHourBeforeUsd, row.ObservedCostPerHourBeforeBytesUsd, row.ObservedCostPerHourBeforeVolumeUsd, + float64Ptr(row.ObservedVolumePerHourBefore), row.ObservedVolumePerHourAfter, + float64Ptr(row.ObservedBytesPerHourBefore), row.ObservedBytesPerHourAfter, + float64Ptr(row.ObservedCostPerHourBeforeUsd), float64Ptr(row.ObservedCostPerHourBeforeBytesUsd), float64Ptr(row.ObservedCostPerHourBeforeVolumeUsd), row.ObservedCostPerHourAfterUsd, row.ObservedCostPerHourAfterBytesUsd, row.ObservedCostPerHourAfterVolumeUsd, ) } diff --git a/internal/upload/conversation_handler.go b/internal/upload/conversation_handler.go index 8c5cb69..e27425a 100644 --- a/internal/upload/conversation_handler.go +++ b/internal/upload/conversation_handler.go @@ -47,6 +47,7 @@ func (h *conversationHandler) Handle(ctx context.Context, entry *db.CrudEntry, e } func (h *conversationHandler) handlePut(ctx context.Context, entry *db.CrudEntry) error { + accountID, _ := entry.Data["account_id"].(string) workspaceID, _ := entry.Data["workspace_id"].(string) title, _ := entry.Data["title"].(string) @@ -57,6 +58,7 @@ func (h *conversationHandler) handlePut(ctx context.Context, entry *db.CrudEntry _, err = h.conversations.Create(ctx, graphql.CreateConversationInput{ ID: id, + AccountID: domain.AccountID(accountID), WorkspaceID: domain.WorkspaceID(workspaceID), Title: title, }) diff --git a/internal/upload/conversation_handler_test.go b/internal/upload/conversation_handler_test.go index f426621..b6b11b3 100644 --- a/internal/upload/conversation_handler_test.go +++ b/internal/upload/conversation_handler_test.go @@ -27,6 +27,7 @@ func TestConversationHandler_Handle(t *testing.T) { testID := uuid.New() var calledWith struct { id uuid.UUID + accountID domain.AccountID workspaceID domain.WorkspaceID title string } @@ -34,6 +35,7 @@ func TestConversationHandler_Handle(t *testing.T) { mock := &apitest.MockConversations{ CreateFunc: func(ctx context.Context, input graphql.CreateConversationInput) (*domain.Conversation, error) { calledWith.id = input.ID + calledWith.accountID = input.AccountID calledWith.workspaceID = input.WorkspaceID calledWith.title = input.Title return &domain.Conversation{ID: domain.ConversationID(input.ID.String())}, nil @@ -46,6 +48,7 @@ func TestConversationHandler_Handle(t *testing.T) { Op: db.OpPut, RowID: testID.String(), Data: map[string]any{ + "account_id": "acc-1", "workspace_id": "ws-1", "title": "Test Conversation", }, @@ -59,6 +62,9 @@ func TestConversationHandler_Handle(t *testing.T) { if calledWith.id != testID { t.Errorf("Create called with id = %v, want %v", calledWith.id, testID) } + if calledWith.accountID != "acc-1" { + t.Errorf("Create called with accountID = %q, want %q", calledWith.accountID, "acc-1") + } if calledWith.workspaceID != "ws-1" { t.Errorf("Create called with workspaceID = %q, want %q", calledWith.workspaceID, "ws-1") } diff --git a/internal/upload/uploader_test.go b/internal/upload/uploader_test.go index 6e45934..0e51e87 100644 --- a/internal/upload/uploader_test.go +++ b/internal/upload/uploader_test.go @@ -97,7 +97,7 @@ func TestUploader_Run(t *testing.T) { } convID := uuid.New().String() - dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"workspace_id":"ws-1","title":"Test"}}`) + dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"Test"}}`) conversations := &apitest.MockConversations{ CreateFunc: func(ctx context.Context, input graphql.CreateConversationInput) (*domain.Conversation, error) { @@ -164,8 +164,8 @@ func TestUploader_Run(t *testing.T) { convID1 := uuid.New().String() convID2 := uuid.New().String() - dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID1+`","data":{"workspace_id":"ws-1","title":"First"}}`) - dbtest.InsertCrudEntry(t, testDB, 2, nil, `{"op":"PUT","type":"conversations","id":"`+convID2+`","data":{"workspace_id":"ws-1","title":"Second"}}`) + dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID1+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"First"}}`) + dbtest.InsertCrudEntry(t, testDB, 2, nil, `{"op":"PUT","type":"conversations","id":"`+convID2+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"Second"}}`) conversations := &apitest.MockConversations{ CreateFunc: func(ctx context.Context, input graphql.CreateConversationInput) (*domain.Conversation, error) { @@ -221,7 +221,7 @@ func TestUploader_Run(t *testing.T) { } convID := uuid.New().String() - dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"workspace_id":"ws-1","title":"Test"}}`) + dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"Test"}}`) conversations := &apitest.MockConversations{ CreateFunc: func(ctx context.Context, input graphql.CreateConversationInput) (*domain.Conversation, error) { @@ -280,7 +280,7 @@ func TestUploader_Run(t *testing.T) { } convID := uuid.New().String() - dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"workspace_id":"ws-1","title":"Test"}}`) + dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"Test"}}`) conversations := &apitest.MockConversations{ CreateFunc: func(ctx context.Context, input graphql.CreateConversationInput) (*domain.Conversation, error) { @@ -393,7 +393,7 @@ func TestUploader_Run(t *testing.T) { } convID := uuid.New().String() - dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"workspace_id":"ws-1","title":"Test"}}`) + dbtest.InsertCrudEntry(t, testDB, 1, nil, `{"op":"PUT","type":"conversations","id":"`+convID+`","data":{"account_id":"acc-1","workspace_id":"ws-1","title":"Test"}}`) callCount := 0 conversations := &apitest.MockConversations{