{
  "$schema": "https://decisions.heos.ae/realtime.json",
  "version": "0.1.0",
  "summary": "Catalog of every Supabase Realtime channel the Decisions AI internal API publishes. Use these with @moei/decisions-sdk's subscribeTo* helpers, or directly with @supabase/supabase-js .channel().on('postgres_changes', { ... }).",
  "transport": {
    "kind": "supabase-realtime",
    "publication": "supabase_realtime",
    "websocket_url": "wss://lydtdudifolcdzunlvtu.supabase.co/realtime/v1/websocket"
  },
  "stream_model": {
    "description": "There is no separate per-token streaming endpoint. The run-pipeline worker accumulates LLM output in memory and flushes partial values to row columns every ~1.5s (or every ~1500 new chars, whichever first). Realtime ships each UPDATE to subscribers. Diff against the previous value if you want char-level deltas.",
    "flush_interval_ms_target": 1500,
    "flush_chars_threshold": 1500,
    "monotonicity": "output_text/thinking_summary/sources grow monotonically during a single run, but may reset if pg_cron resumes an abandoned run. Be tolerant of length decreasing across UPDATEs."
  },
  "channels": [
    {
      "name": "decisions",
      "filter": "id=eq.<decision_id>",
      "table": "public.decisions",
      "events": ["INSERT", "UPDATE"],
      "fires_when": "Decision row is updated: status transitions, scope_frame populating, final_markdown populating, Arabic translations arriving.",
      "payload_typescript": "Decision",
      "payload_url": "https://decisions.heos.ae/api.md#decisions",
      "sdk_helper": "subscribeToDecision(client, decisionId, (decision) => ...)",
      "example_payload": {
        "schema": "public",
        "table": "decisions",
        "commit_timestamp": "2026-04-26T19:11:02.493Z",
        "eventType": "UPDATE",
        "new": {
          "id": "f3b1e9c2-2a8d-4f3a-9e9f-3a1f1c8d2c0a",
          "status": "complete",
          "final_markdown": "# Recommendation\n\n..."
        }
      }
    },
    {
      "name": "runs",
      "filter": "decision_id=eq.<decision_id>",
      "table": "public.runs",
      "events": ["INSERT", "UPDATE", "DELETE"],
      "fires_when": "A run row for this decision is created, updated, or deleted. Useful to detect status flipping to complete/failed and to surface runs.error.",
      "payload_typescript": "Run",
      "payload_url": "https://decisions.heos.ae/api.md#runs",
      "sdk_helper": "subscribeToRun(client, decisionId, (run, event) => ...)",
      "negative_space": "Re-runs produce additional rows for the same decision. The latest by started_at is the active one."
    },
    {
      "name": "run_steps",
      "filter": "run_id=eq.<run_id>",
      "table": "public.run_steps",
      "events": ["INSERT", "UPDATE", "DELETE"],
      "fires_when": "Per-step partial output. This is the per-token stream — output_text, thinking_summary, sources, and last_streamed_at are flushed every ~1.5s while the agent is generating.",
      "payload_typescript": "RunStep",
      "payload_url": "https://decisions.heos.ae/api.md#run_steps",
      "sdk_helper": "subscribeToRunSteps(client, runId, (step, event) => ...)",
      "negative_space": "Status can flip backward (complete -> running) when pg_cron resumes an abandoned run; output_text length can also decrease in that case. Treat each UPDATE independently."
    },
    {
      "name": "chat_messages",
      "filter": "decision_id=eq.<decision_id>",
      "table": "public.chat_messages",
      "events": ["INSERT", "UPDATE", "DELETE"],
      "fires_when": "User insert (your client does this) or assistant insert (done by /functions/v1/chat once its SSE stream completes).",
      "payload_typescript": "ChatMessage",
      "payload_url": "https://decisions.heos.ae/api.md#chat_messages",
      "sdk_helper": "subscribeToChat(client, decisionId, (message, event) => ...)",
      "negative_space": "There is no per-token Realtime stream for chat. Use the SSE response from POST /functions/v1/chat for per-token deltas; Realtime only sees the final assistant message."
    }
  ],
  "subscribing_with_supabase_js": {
    "example_typescript": "import { createClient } from '@supabase/supabase-js';\nconst supabase = createClient(URL, ANON_KEY);\nconst channel = supabase\n  .channel(`run_steps:${runId}`)\n  .on('postgres_changes', {\n    event: '*',\n    schema: 'public',\n    table: 'run_steps',\n    filter: `run_id=eq.${runId}`,\n  }, (payload) => console.log(payload))\n  .subscribe();\n// Later:\nawait supabase.removeChannel(channel);"
  }
}
