lib/openagents/observability/readback.ex

main at 58e6347eeb72 · 3 KB

defmodule OpenAgents.Observability.Readback do
  @moduledoc "Bounded aggregate read-back over authoritative receipts, without private content."

  alias OpenAgents.Repo

  @planes %{
    "provider" => {"turn_provider_steps", "status"},
    "tool" => {"turn_tool_steps", "status"},
    "memory" => {"profile_memory_records", "status"},
    "module" => {"module_route_receipts", "status"},
    "collective" => {"collective_candidates", "status"},
    "evaluation" => {"program_lifecycle_artifacts", "stage"}
  }

  @spec snapshot() :: map()
  def snapshot do
    voice_config = OpenAgents.Voice.Config.current!()

    %{
      schema: "openagents.observability.readback.v1",
      captured_at: DateTime.utc_now() |> DateTime.to_iso8601(),
      persona_id: OpenAgents.Persona.current!().id,
      persona_digest: OpenAgents.Persona.current!().digest,
      text_model_id: Application.fetch_env!(:openagents, :openai_model),
      voice_model_id: voice_config.model,
      voice_artifact_id: "sarah.voice.openai.#{voice_config.voice}.v1",
      module_registry_digest: OpenAgents.Tools.Registry.current!().digest,
      planes: Map.new(@planes, fn {plane, source} -> {plane, status_counts(source)} end),
      integrity: integrity_counts()
    }
  end

  defp status_counts({table, column}) do
    sql =
      "SELECT #{column}, count(*)::bigint FROM #{table} GROUP BY #{column} ORDER BY #{column} LIMIT 32"

    sql
    |> Repo.query!([])
    |> Map.fetch!(:rows)
    |> Map.new(fn [status, count] -> {status, count} end)
  end

  defp integrity_counts do
    %{
      "cross_scope_private_leakage" =>
        scalar("""
        SELECT count(*)::bigint FROM collective_candidates c
        JOIN collective_consent_receipts r ON r.id=c.consent_receipt_id
        WHERE c.visitor_id<>r.visitor_id OR c.source_scope_digest<>r.source_scope_digest
        """),
      "missing_collective_consent" =>
        scalar("""
        SELECT count(*)::bigint FROM collective_candidates c
        LEFT JOIN collective_consent_receipts r ON r.id=c.consent_receipt_id
        WHERE r.id IS NULL
        """),
      "missing_turn_provenance" =>
        scalar("""
        SELECT count(*)::bigint FROM turn_receipts
        WHERE status<>'captured' AND
          (persona_id IS NULL OR persona_digest IS NULL OR role_id IS NULL OR
           role_digest IS NULL OR instruction_digest IS NULL OR input_digest IS NULL)
        """),
      "missing_executor_disclosure" =>
        scalar("""
        SELECT count(*)::bigint FROM turn_tool_steps
        WHERE status NOT IN ('requested','running') AND
          (executor_id IS NULL OR executor_id='' OR executor_disclosure IS NULL OR executor_disclosure='')
        """),
      "failed_attribution_reconciliation" =>
        scalar("""
        SELECT count(*)::bigint FROM turn_tool_steps s
        LEFT JOIN compensation_events e ON e.tool_step_id=s.id
        WHERE s.status='succeeded' AND s.billable=true AND e.id IS NULL
        """),
      "stuck_turns" =>
        scalar("""
        SELECT count(*)::bigint FROM turns
        WHERE status IN ('queued','running') AND updated_at < now() - interval '5 minutes'
        """),
      "stuck_tool_steps" =>
        scalar("""
        SELECT count(*)::bigint FROM turn_tool_steps
        WHERE status IN ('requested','running') AND inserted_at < now() - interval '5 minutes'
        """)
    }
  end

  defp scalar(sql) do
    %{rows: [[value]]} = Repo.query!(sql, [])
    value
  end
end