-- Task #242 (B1) — Capability 通用框架的 source-controlled DDL。
--
-- 这个文件是本任务唯一的"权威 schema 来源",运行时由 applyCapabilityMigrations()
-- 在 boot 时 idempotent 地执行。原因:
--   1. 4 张新表 + partial unique index + view aliases 都用 IF NOT EXISTS / OR
--      REPLACE,可以重复跑;
--   2. drizzle-kit push 在 interactive prompt 上阻塞,且 partial WHERE 索引、
--      VIEW alias 都不在 drizzle DSL 表达力之内,所以 raw SQL 是唯一 reproducible
--      的 source-of-truth;
--   3. 任何环境(dev/prod/CI)只要这个文件 commit 进 repo,跑 boot 就能拿到一致
--      的 schema。

-- ============================================================ Tables

CREATE TABLE IF NOT EXISTS "capabilities" (
  "id" text PRIMARY KEY NOT NULL,
  "name" text NOT NULL UNIQUE,
  "domain" text NOT NULL,
  "description" text NOT NULL DEFAULT '',
  "current_lifecycle_state" text NOT NULL DEFAULT 'created',
  "bound_network_name" text,
  "meta" jsonb NOT NULL DEFAULT '{}'::jsonb,
  "created_at" timestamptz NOT NULL DEFAULT now(),
  "updated_at" timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS "capabilities_domain_idx" ON "capabilities" ("domain");
CREATE INDEX IF NOT EXISTS "capabilities_state_idx" ON "capabilities" ("current_lifecycle_state");

CREATE TABLE IF NOT EXISTS "capability_lifecycle_events" (
  "id" text PRIMARY KEY NOT NULL,
  "capability_id" text NOT NULL,
  "from_state" text,
  "to_state" text NOT NULL,
  "reason" text NOT NULL,
  "payload" jsonb NOT NULL DEFAULT '{}'::jsonb,
  "created_at" timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS "capability_lifecycle_events_cap_idx" ON "capability_lifecycle_events" ("capability_id");
CREATE INDEX IF NOT EXISTS "capability_lifecycle_events_created_at_idx" ON "capability_lifecycle_events" ("created_at");

CREATE TABLE IF NOT EXISTS "capability_inner_loop_runs" (
  "id" text PRIMARY KEY NOT NULL,
  "capability_id" text NOT NULL,
  "steps_json" jsonb NOT NULL,
  "source_run_id" text,
  "duration_ms" integer,
  "status" text NOT NULL DEFAULT 'ok',
  "created_at" timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS "capability_inner_loop_runs_cap_idx" ON "capability_inner_loop_runs" ("capability_id");
CREATE INDEX IF NOT EXISTS "capability_inner_loop_runs_created_at_idx" ON "capability_inner_loop_runs" ("created_at");

CREATE TABLE IF NOT EXISTS "network_dispatch_violations" (
  "id" text PRIMARY KEY NOT NULL,
  "network_name" text NOT NULL,
  "tool_name" text NOT NULL,
  "args_hash" text NOT NULL,
  "details_json" jsonb NOT NULL,
  "mode" text NOT NULL DEFAULT 'enforced',
  "conversation_id" text,
  "owner_user_id" text,
  "created_at" timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS "network_dispatch_violations_net_idx" ON "network_dispatch_violations" ("network_name");
CREATE INDEX IF NOT EXISTS "network_dispatch_violations_created_at_idx" ON "network_dispatch_violations" ("created_at");
CREATE UNIQUE INDEX IF NOT EXISTS "network_dispatch_violations_uniq_recent" ON "network_dispatch_violations" ("network_name","args_hash","created_at");

-- ============================================================ Partial unique index
-- 漏洞 4 真 idempotent 的关键:并发请求竞争 user-private active variant 时,
-- 第二个 INSERT 必须被 DB 拒绝(SQLSTATE 23505),getOrCreateUserPrivateVariant
-- 的外层 retry 会重新 select winner 行。
CREATE UNIQUE INDEX IF NOT EXISTS "network_versions_uniq_active_private"
  ON "network_versions" ("network_id","private_namespace")
  WHERE "status" = 'active' AND "private_namespace" IS NOT NULL;

-- ============================================================ Capability_* alias views
-- task plan 第 81 行:为 B2-B11 提供新名字读旧表的 view alias。
-- 实现策略:capability 一旦 boundNetworkName 指向 tool_networks.name,就可以
-- 通过这 2 个 view 用 capability 视角读到该 network 的 versions / promotions。
-- 真 capability 自有数据在 capability_data_foundation / capability_inner_loop_runs。

CREATE OR REPLACE VIEW "capability_versions" AS
SELECT
  c."id"                AS "capability_id",
  c."name"              AS "capability_name",
  nv."id"               AS "version_id",
  nv."network_id"       AS "network_id",
  nv."version_label"    AS "version_label",
  nv."status"           AS "status",
  nv."private_namespace" AS "private_namespace",
  nv."built_by"         AS "built_by",
  nv."builder_model_tier" AS "builder_model_tier",
  nv."internal_graph"   AS "internal_graph",
  nv."config"           AS "config",
  nv."created_at"       AS "created_at"
FROM "capabilities" c
JOIN "tool_networks" tn ON tn."name" = c."bound_network_name"
JOIN "network_versions" nv ON nv."network_id" = tn."id";

CREATE OR REPLACE VIEW "capability_evolution_events" AS
SELECT
  c."id"                AS "capability_id",
  c."name"              AS "capability_name",
  np."id"               AS "promotion_id",
  np."network_id"       AS "network_id",
  np."from_variant_id"  AS "from_variant_id",
  np."to_variant_id"    AS "to_variant_id",
  np."reason"           AS "reason",
  np."decided_by"       AS "decided_by",
  np."created_at"       AS "created_at"
FROM "capabilities" c
JOIN "tool_networks" tn ON tn."name" = c."bound_network_name"
JOIN "network_promotions" np ON np."network_id" = tn."id";
