-- AwoooP Phase 4: Platform Shell in Shadow Mode
-- Run State Machine 持久化表
-- 2026-05-04 ogt + Claude Sonnet 4.6（ADR-114/ADR-119）
--
-- 前置：Phase 1 control plane（awooop_projects）必須已執行
--
-- 三表：
--   awooop_run_state        — Run FSM 主表（lease + heartbeat + SKIP LOCKED）
--   awooop_run_step_journal — SAGA step journal（tool call + 補償指令，ADR-119）
--   awooop_run_idempotency  — 去重冪等表（ADR-114）

-- =========================================================
-- STEP 1: awooop_run_state
-- =========================================================
CREATE TABLE IF NOT EXISTS awooop_run_state (
    run_id          UUID            PRIMARY KEY,
    project_id      VARCHAR(64)     NOT NULL REFERENCES awooop_projects(project_id),
    agent_id        VARCHAR(128)    NOT NULL,

    -- FSM 狀態
    state           VARCHAR(32)     NOT NULL DEFAULT 'pending'
                    CHECK (state IN (
                        'pending','running','waiting_tool',
                        'waiting_approval','completed','failed',
                        'cancelled','timeout'
                    )),

    -- Worker lease（SKIP LOCKED 防 double-pickup）
    lease_until     TIMESTAMPTZ,
    heartbeat_at    TIMESTAMPTZ,
    worker_id       VARCHAR(128),

    -- Retry 計數
    attempt_count   SMALLINT        NOT NULL DEFAULT 0,
    max_attempts    SMALLINT        NOT NULL DEFAULT 3,

    -- Observability
    trace_id        VARCHAR(128),

    -- Trigger 來源
    trigger_type    VARCHAR(32),
    trigger_ref     VARCHAR(256),               -- channel_event_id / schedule_id / etc.

    -- Shadow mode flag
    is_shadow       BOOLEAN         NOT NULL DEFAULT TRUE,

    -- Artifact integrity（ADR-112）
    input_sha256    CHAR(64),
    output_sha256   CHAR(64),

    -- Budget
    cost_usd        NUMERIC(10, 4)  NOT NULL DEFAULT 0.0000,
    step_count      SMALLINT        NOT NULL DEFAULT 0,

    -- 結果
    error_code      VARCHAR(64),
    error_detail    TEXT,

    -- 時間戳記
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
    started_at      TIMESTAMPTZ,
    completed_at    TIMESTAMPTZ,
    timeout_at      TIMESTAMPTZ
);

COMMENT ON TABLE awooop_run_state IS
    'ADR-114: Run FSM 主表，SKIP LOCKED worker lease';
COMMENT ON COLUMN awooop_run_state.is_shadow IS
    'Phase 4 shadow mode：TRUE = 不產生 user response，不執行 destructive tool';

-- Index: worker 掃 PENDING（SKIP LOCKED 用）
CREATE INDEX IF NOT EXISTS idx_run_state_pending
    ON awooop_run_state (project_id, created_at)
    WHERE state = 'pending' AND lease_until IS NULL;

-- Index: stale run reaper（找 lease 過期的 running run）
CREATE INDEX IF NOT EXISTS idx_run_state_stale
    ON awooop_run_state (lease_until)
    WHERE state = 'running' AND lease_until IS NOT NULL;

-- Index: project timeline（dashboard 查詢）
CREATE INDEX IF NOT EXISTS idx_run_state_project_timeline
    ON awooop_run_state (project_id, created_at DESC);

-- Index: trace_id（跨系統追蹤）
CREATE INDEX IF NOT EXISTS idx_run_state_trace_id
    ON awooop_run_state (trace_id)
    WHERE trace_id IS NOT NULL;

-- =========================================================
-- STEP 2: awooop_run_step_journal（SAGA step journal，ADR-119）
-- =========================================================
CREATE TABLE IF NOT EXISTS awooop_run_step_journal (
    step_id         UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    run_id          UUID            NOT NULL REFERENCES awooop_run_state(run_id) ON DELETE CASCADE,
    project_id      VARCHAR(64)     NOT NULL,

    -- Step 順序（每個 run 內遞增）
    step_seq        SMALLINT        NOT NULL,

    -- Tool call 資訊
    tool_name       VARCHAR(128)    NOT NULL,
    mcp_gateway_id  VARCHAR(128),

    -- Artifact integrity（ADR-112）
    input_hash      CHAR(64),
    output_hash     CHAR(64),

    -- SAGA 補償指令（JSON）
    compensation_json JSONB,

    -- 執行結果
    result_status   VARCHAR(16)     NOT NULL DEFAULT 'pending'
                    CHECK (result_status IN ('pending','success','failed','compensated')),
    error_code      VARCHAR(64),

    -- Shadow 攔截記錄
    was_blocked     BOOLEAN         NOT NULL DEFAULT FALSE,
    block_reason    VARCHAR(128),

    -- 時間
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
    completed_at    TIMESTAMPTZ,
    latency_ms      INTEGER
);

COMMENT ON TABLE awooop_run_step_journal IS
    'ADR-119 SAGA step journal：每個 tool call 獨立記錄 + 補償指令';

CREATE UNIQUE INDEX IF NOT EXISTS uix_run_step_seq
    ON awooop_run_step_journal (run_id, step_seq);

CREATE INDEX IF NOT EXISTS idx_run_step_run_id
    ON awooop_run_step_journal (run_id, step_seq);

-- =========================================================
-- STEP 3: awooop_run_idempotency（ADR-114 去重冪等）
-- =========================================================
CREATE TABLE IF NOT EXISTS awooop_run_idempotency (
    idempotency_id  UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id      VARCHAR(64)     NOT NULL,
    channel_type    VARCHAR(32)     NOT NULL,
    provider_event_id VARCHAR(256)  NOT NULL,

    -- 映射到的 run
    run_id          UUID            NOT NULL REFERENCES awooop_run_state(run_id),

    created_at      TIMESTAMPTZ     NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE awooop_run_idempotency IS
    'ADR-114: (project_id, channel_type, provider_event_id) → run_id 去重';

CREATE UNIQUE INDEX IF NOT EXISTS uix_run_idempotency_key
    ON awooop_run_idempotency (project_id, channel_type, provider_event_id);

CREATE INDEX IF NOT EXISTS idx_run_idempotency_run_id
    ON awooop_run_idempotency (run_id);

-- =========================================================
-- STEP 4: RLS（ADR-118 多租戶隔離）
-- =========================================================
ALTER TABLE awooop_run_state       ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_run_state       FORCE ROW LEVEL SECURITY;
ALTER TABLE awooop_run_step_journal ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_run_step_journal FORCE ROW LEVEL SECURITY;
ALTER TABLE awooop_run_idempotency ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_run_idempotency FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS run_state_tenant_isolation ON awooop_run_state;
CREATE POLICY run_state_tenant_isolation ON awooop_run_state
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

DROP POLICY IF EXISTS run_step_journal_tenant_isolation ON awooop_run_step_journal;
CREATE POLICY run_step_journal_tenant_isolation ON awooop_run_step_journal
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

DROP POLICY IF EXISTS run_idempotency_tenant_isolation ON awooop_run_idempotency;
CREATE POLICY run_idempotency_tenant_isolation ON awooop_run_idempotency
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

-- =========================================================
-- STEP 5: GRANT
-- =========================================================
GRANT SELECT, INSERT, UPDATE ON awooop_run_state TO awooop_app;
GRANT SELECT, INSERT, UPDATE ON awooop_run_step_journal TO awooop_app;
GRANT SELECT, INSERT ON awooop_run_idempotency TO awooop_app;

-- =========================================================
-- 驗收查詢
-- =========================================================
-- SELECT tablename, rowsecurity FROM pg_tables
--   WHERE tablename IN ('awooop_run_state','awooop_run_step_journal','awooop_run_idempotency');
-- 預期：所有 rowsecurity = true
