-- ============================================================
-- 63_kb_aso_masking_rule_runtime.sql
--
-- Synchronizes CB_MASKING_RULE / CB_COLUMN_MASKING_RULE and
-- CB_USER_MASKING_RULE into Oracle Data Redaction policies.
--
-- Important behaviour
--   * One selected masking rule is applied to each protected column.
--   * A direct user rule controls whether that user is masked or receives
--     an original-value exception (UNMASK).
--   * VPD still determines row visibility. Existing VPD NULL policies
--     remain in force for users without column permission.
--
-- Run as ADMIN with SQLcl after 62 to initialize the runtime package, or as
-- a manual recovery/reconciliation action. The backoffice synchronizes
-- DBMS_REDACT on normal configuration changes; this script follows the same
-- source-of-truth rule for existing installations.
-- Requires EXECUTE on DBMS_REDACT and ADMINISTER REDACTION POLICY for POC_2
-- objects.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET DEFINE OFF
SET FEEDBACK ON

PROMPT === Validating masking-rule metadata ===
DECLARE
  PROCEDURE require_table(p_table_name IN VARCHAR2) IS
    v_count NUMBER;
  BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM user_tables
    WHERE table_name = p_table_name;
    IF v_count = 0 THEN
      RAISE_APPLICATION_ERROR(-20001,
        'Missing ' || p_table_name || '. Run 62_kb_aso_masking_backoffice_metadata.sql first.');
    END IF;
  END;
BEGIN
  require_table('CB_MASKING_RULE');
  require_table('CB_COLUMN_MASKING_RULE');
  require_table('CB_USER_MASKING_RULE');
END;
/

PROMPT === Updating trusted bearer context with column exception flags ===
CREATE OR REPLACE PACKAGE cb_agent_ctx_pkg AUTHID DEFINER AS
  PROCEDURE clear_user;
  PROCEDURE set_user(p_user_id IN NUMBER);
  PROCEDURE set_user_values(
    p_user_id           IN NUMBER,
    p_emp_no            IN VARCHAR2,
    p_dept_code         IN VARCHAR2,
    p_can_read_contents IN VARCHAR2
  );
  PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2);
END;
/

CREATE OR REPLACE PACKAGE BODY cb_agent_ctx_pkg AS
  PROCEDURE clear_stakeholder_context AS
  BEGIN
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', NULL);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE', NULL);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL', NULL);
  END;

  PROCEDURE clear_masking_rule_context AS
  BEGIN
    FOR r IN (SELECT column_id FROM cb_column_masking_rule) LOOP
      DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'MR_' || TO_CHAR(r.column_id), NULL);
    END LOOP;
  END;

  PROCEDURE set_masking_rule_context(p_user_id IN NUMBER) AS
  BEGIN
    clear_masking_rule_context;
    FOR r IN (
      SELECT link.column_id,
             CASE WHEN EXISTS (
               SELECT 1
               FROM cb_user_masking_rule user_rule
               WHERE user_rule.user_id = p_user_id
                 AND user_rule.column_id = link.column_id
                 AND user_rule.active_yn = 'Y'
                 AND user_rule.decision = 'UNMASK'
             ) THEN 'Y' ELSE 'N' END AS can_unmask
      FROM cb_column_masking_rule link
      JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
      WHERE rule.enabled_yn = 'Y'
    ) LOOP
      DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'MR_' || TO_CHAR(r.column_id), r.can_unmask);
    END LOOP;
  END;

  PROCEDURE clear_user AS
  BEGIN
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', NULL);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', NULL);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', NULL);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', NULL);
    clear_stakeholder_context;
    clear_masking_rule_context;
  END;

  PROCEDURE set_user_values(
    p_user_id           IN NUMBER,
    p_emp_no            IN VARCHAR2,
    p_dept_code         IN VARCHAR2,
    p_can_read_contents IN VARCHAR2
  ) AS
  BEGIN
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', TO_CHAR(p_user_id));
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', p_emp_no);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', p_dept_code);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', p_can_read_contents);
    clear_stakeholder_context;
    clear_masking_rule_context;
  END;

  PROCEDURE set_stakeholder_context(
    p_stakeholder_user_id IN VARCHAR2,
    p_role                IN VARCHAR2,
    p_channel             IN VARCHAR2
  ) AS
    v_channel VARCHAR2(50);
  BEGIN
    v_channel := CASE TRIM(p_channel)
      WHEN '설계사채널' THEN '설계사'
      WHEN 'GA채널' THEN 'GA'
      ELSE TRIM(p_channel)
    END;
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', p_stakeholder_user_id);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE', p_role);
    DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL', v_channel);
  END;

  PROCEDURE set_user(p_user_id IN NUMBER) AS
    v_emp_no            cb_app_user.employee_no%TYPE;
    v_dept_code         cb_app_user.dept_code%TYPE;
    v_can_read_contents cb_app_user.can_read_contents%TYPE;
  BEGIN
    SELECT employee_no,
           dept_code,
           CASE
             WHEN EXISTS (
               SELECT 1
               FROM cb_user_role ur
               JOIN cb_permission p ON p.role_id = ur.role_id
               JOIN cb_permission_column pc ON pc.permission_id = p.perm_id
               WHERE ur.user_id = p_user_id
                 AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
                 AND p.action_name = 'SELECT'
                 AND pc.column_name = 'CONTENTS'
             ) THEN 'Y'
             ELSE 'N'
           END
    INTO v_emp_no, v_dept_code, v_can_read_contents
    FROM cb_app_user
    WHERE user_id = p_user_id
      AND active = 'Y';

    set_user_values(p_user_id, v_emp_no, v_dept_code, v_can_read_contents);
    set_masking_rule_context(p_user_id);
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      clear_user;
      RAISE_APPLICATION_ERROR(-20003, 'Mapped application user not found');
  END;

  PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2) AS
    v_user_id             cb_app_user.user_id%TYPE;
    v_emp_no              cb_app_user.employee_no%TYPE;
    v_dept_code           cb_app_user.dept_code%TYPE;
    v_can_read_contents   cb_app_user.can_read_contents%TYPE;
    v_stakeholder_user_id VARCHAR2(30);
    v_stakeholder_role    VARCHAR2(50);
    v_stakeholder_channel VARCHAR2(50);
  BEGIN
    IF p_bearer_key IS NULL THEN
      clear_user;
      RAISE_APPLICATION_ERROR(-20001, 'Authorization Bearer key is required');
    END IF;

    SELECT u.user_id,
           u.employee_no,
           u.dept_code,
           CASE
             WHEN EXISTS (
               SELECT 1
               FROM cb_user_role ur
               JOIN cb_permission p ON p.role_id = ur.role_id
               JOIN cb_permission_column pc ON pc.permission_id = p.perm_id
               WHERE ur.user_id = u.user_id
                 AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
                 AND p.action_name = 'SELECT'
                 AND pc.column_name = 'CONTENTS'
             ) THEN 'Y'
             ELSE 'N'
           END,
           COALESCE(k.stakeholder_user_id, u.stakeholder_user_id)
    INTO v_user_id, v_emp_no, v_dept_code, v_can_read_contents, v_stakeholder_user_id
    FROM cb_agent_bearer_key k
    JOIN cb_app_user u ON u.user_id = k.user_id
    WHERE k.key_hash = STANDARD_HASH(p_bearer_key, 'SHA256')
      AND k.active = 'Y'
      AND k.revoked_at IS NULL
      AND (k.expires_at IS NULL OR k.expires_at > SYSDATE)
      AND u.active = 'Y';

    set_user_values(v_user_id, v_emp_no, v_dept_code, v_can_read_contents);

    IF v_stakeholder_user_id IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', v_stakeholder_user_id);
      SELECT role, channel
      INTO v_stakeholder_role, v_stakeholder_channel
      FROM poc_2.kb_stakeholders
      WHERE user_id = v_stakeholder_user_id;
      set_stakeholder_context(v_stakeholder_user_id, v_stakeholder_role, v_stakeholder_channel);
    END IF;

    set_masking_rule_context(v_user_id);
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      clear_user;
      RAISE_APPLICATION_ERROR(-20002, 'Invalid or expired Bearer key');
  END;
END;
/
SHOW ERRORS PACKAGE BODY cb_agent_ctx_pkg

GRANT EXECUTE ON cb_agent_ctx_pkg TO cb_ords;

PROMPT === Preserving the all-access admin raw-display contract ===
MERGE INTO cb_user_masking_rule dst
USING (
  SELECT user_row.user_id, column_rule.column_id
  FROM cb_app_user user_row
  CROSS JOIN cb_column_masking_rule column_rule
  JOIN cb_masking_rule rule ON rule.rule_id = column_rule.rule_id
  WHERE user_row.stakeholder_user_id = 'KB_VPD_ADMIN'
    AND user_row.active = 'Y'
    AND rule.enabled_yn = 'Y'
) src
ON (dst.user_id = src.user_id AND dst.column_id = src.column_id)
WHEN NOT MATCHED THEN
  INSERT (user_id, column_id, decision, active_yn, updated_at)
  VALUES (src.user_id, src.column_id, 'UNMASK', 'Y', SYSTIMESTAMP);
COMMIT;

PROMPT === Removing stale columns and disabling unconfigured KB policies ===
-- A policy definition is deliberately retained when no column is configured.
-- DISABLE_POLICY makes the effective state match the empty backoffice setting
-- and lets a later column assignment re-enable the same named policy safely.
DECLARE
  PROCEDURE reconcile_existing_policy(
      p_object_name IN VARCHAR2,
      p_policy_name IN VARCHAR2
  ) IS
    v_policy_count NUMBER;
    v_configured_count NUMBER;
  BEGIN
    SELECT COUNT(*) INTO v_policy_count
    FROM redaction_policies
    WHERE object_owner = 'POC_2' AND object_name = p_object_name AND policy_name = p_policy_name;
    IF v_policy_count = 0 THEN
      RETURN;
    END IF;

    SELECT COUNT(*) INTO v_configured_count
    FROM cb_column_masking_rule link
    JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
    JOIN cb_protected_column column_row ON column_row.column_id = link.column_id
    JOIN cb_protected_object object_row ON object_row.object_id = column_row.object_id
    WHERE object_row.owner = 'POC_2'
      AND object_row.object_name = p_object_name
      AND rule.enabled_yn = 'Y';

    IF v_configured_count = 0 THEN
      DBMS_REDACT.DISABLE_POLICY('POC_2', p_object_name, p_policy_name);
      RETURN;
    END IF;

    FOR stale_column IN (
      SELECT policy_column.column_name
      FROM redaction_columns policy_column
      WHERE policy_column.object_owner = 'POC_2'
        AND policy_column.object_name = p_object_name
        AND NOT EXISTS (
          SELECT 1
          FROM cb_column_masking_rule link
          JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
          JOIN cb_protected_column column_row ON column_row.column_id = link.column_id
          JOIN cb_protected_object object_row ON object_row.object_id = column_row.object_id
          WHERE object_row.owner = 'POC_2'
            AND object_row.object_name = p_object_name
            AND column_row.column_name = policy_column.column_name
            AND rule.enabled_yn = 'Y'
        )
    ) LOOP
      DBMS_REDACT.ALTER_POLICY(
        object_schema => 'POC_2', object_name => p_object_name, policy_name => p_policy_name,
        action => DBMS_REDACT.DROP_COLUMN, column_name => stale_column.column_name
      );
    END LOOP;
  END;
BEGIN
  reconcile_existing_policy('KB_CUSTOMERS', 'KB_CUSTOMER_PII_REDACT');
  reconcile_existing_policy('KB_CLAIMS', 'KB_CLAIM_AMOUNT_REDACT');
  reconcile_existing_policy('KB_CONTRACTS', 'KB_CONTRACT_PREMIUM_REDACT');
  reconcile_existing_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_HOLDING_REDACT');
END;
/

PROMPT === Integrating configured columns into the existing KB Data Redaction policies ===
DECLARE
  v_policy_name     VARCHAR2(128);
  v_expression_name VARCHAR2(128);
  v_expression      VARCHAR2(4000);

  PROCEDURE require_existing_policy(
      p_object_name IN VARCHAR2,
      p_policy_name IN VARCHAR2
  ) IS
    v_count NUMBER;
  BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM redaction_policies
    WHERE object_owner = 'POC_2'
      AND object_name = p_object_name
      AND policy_name = p_policy_name;
    IF v_count = 0 THEN
      RAISE_APPLICATION_ERROR(-20012,
        'Existing Data Redaction policy not found: POC_2.' || p_object_name || '.' || p_policy_name);
    END IF;
  END;

  PROCEDURE upsert_expression(
      p_expression_name IN VARCHAR2,
      p_expression      IN VARCHAR2
  ) IS
    v_count NUMBER;
  BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM redaction_expressions
    WHERE policy_expression_name = p_expression_name;
    IF v_count = 0 THEN
      DBMS_REDACT.CREATE_POLICY_EXPRESSION(
        policy_expression_name        => p_expression_name,
        expression                    => p_expression,
        policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
      );
    ELSE
      DBMS_REDACT.UPDATE_POLICY_EXPRESSION(
        policy_expression_name        => p_expression_name,
        expression                    => p_expression,
        policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
      );
    END IF;
  END;

  PROCEDURE modify_column(
      p_object_name   IN VARCHAR2,
      p_policy_name   IN VARCHAR2,
      p_column_name   IN VARCHAR2,
      p_template_code IN VARCHAR2
  ) IS
    PROCEDURE apply(
        p_function_type IN BINARY_INTEGER,
        p_pattern       IN VARCHAR2 DEFAULT NULL,
        p_replace       IN VARCHAR2 DEFAULT NULL
    ) IS
    BEGIN
      DBMS_REDACT.ALTER_POLICY(
        object_schema         => 'POC_2',
        object_name           => p_object_name,
        policy_name           => p_policy_name,
        action                => DBMS_REDACT.MODIFY_COLUMN,
        column_name           => p_column_name,
        function_type         => p_function_type,
        regexp_pattern        => p_pattern,
        regexp_replace_string => p_replace
      );
    END;
  BEGIN
    CASE p_template_code
      WHEN 'NULLIFY' THEN
        apply(DBMS_REDACT.NULLIFY);
      WHEN 'FULL' THEN
        apply(DBMS_REDACT.FULL);
      WHEN 'TEXT_PARTIAL' THEN
        apply(DBMS_REDACT.REGEXP, '(^.).*$', '\1***');
      WHEN 'RRN_PARTIAL' THEN
        apply(DBMS_REDACT.REGEXP, '(^[0-9]{6})-?[0-9]{7}$', '\1-*******');
      ELSE
        RAISE_APPLICATION_ERROR(-20013, 'Unsupported masking template: ' || p_template_code);
    END CASE;
  END;

BEGIN
  FOR configured_column IN (
    SELECT protected_object.object_name,
           protected_column.column_id,
           protected_column.column_name,
           masking_rule.template_code
    FROM cb_column_masking_rule link
    JOIN cb_masking_rule masking_rule ON masking_rule.rule_id = link.rule_id
    JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
    JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
    WHERE protected_object.owner = 'POC_2'
      AND masking_rule.enabled_yn = 'Y'
    ORDER BY protected_object.object_name, protected_column.column_id
  ) LOOP
    v_policy_name := CASE configured_column.object_name
      WHEN 'KB_CUSTOMERS' THEN 'KB_CUSTOMER_PII_REDACT'
      WHEN 'KB_CLAIMS' THEN 'KB_CLAIM_AMOUNT_REDACT'
      WHEN 'KB_CONTRACTS' THEN 'KB_CONTRACT_PREMIUM_REDACT'
      WHEN 'KB_EXTERNAL_HOLDINGS' THEN 'KB_EXT_HOLDING_REDACT'
      ELSE NULL
    END;
    IF v_policy_name IS NULL THEN
      RAISE_APPLICATION_ERROR(-20014,
        'No existing KB Data Redaction policy mapping for ' || configured_column.object_name);
    END IF;

    require_existing_policy(configured_column.object_name, v_policy_name);
    DBMS_REDACT.ENABLE_POLICY('POC_2', configured_column.object_name, v_policy_name);
    modify_column(
      configured_column.object_name,
      v_policy_name,
      configured_column.column_name,
      configured_column.template_code
    );

    v_expression_name := 'CBMR_' || TO_CHAR(configured_column.column_id);
    v_expression := 'SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(configured_column.column_id)
      || ''') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(configured_column.column_id) || ''') <> ''Y''';
    upsert_expression(v_expression_name, v_expression);
    DBMS_REDACT.APPLY_POLICY_EXPR_TO_COL(
      object_schema          => 'POC_2',
      object_name            => configured_column.object_name,
      column_name            => configured_column.column_name,
      policy_expression_name => v_expression_name
    );
  END LOOP;
END;
/
COMMIT;

PROMPT === Verification ===
SELECT object_name, policy_name, expression
FROM redaction_policies
WHERE object_owner = 'POC_2'
  AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_CONTRACTS', 'KB_EXTERNAL_HOLDINGS')
ORDER BY object_name, policy_name;

SELECT object_name, column_name, function_type
FROM redaction_columns
WHERE object_owner = 'POC_2'
  AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_CONTRACTS', 'KB_EXTERNAL_HOLDINGS')
ORDER BY object_name, column_name;

SELECT policy_expression_name, object_name, column_name, expression
FROM redaction_expressions
WHERE policy_expression_name LIKE 'CBMR_%'
ORDER BY policy_expression_name;

SELECT user_row.user_name,
       protected_object.object_name,
       protected_column.column_name,
       user_rule.decision
FROM cb_user_masking_rule user_rule
JOIN cb_app_user user_row ON user_row.user_id = user_rule.user_id
JOIN cb_protected_column protected_column ON protected_column.column_id = user_rule.column_id
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
ORDER BY user_row.user_name, protected_object.object_name, protected_column.column_name;
EXIT

-- Legacy replacement implementation retained below for migration reference.
-- SQLcl exits above, so it never executes.
PROMPT === Rebuilding managed Oracle Data Redaction policies ===
DECLARE
  v_policy_name VARCHAR2(128);
  v_expression_name VARCHAR2(128);
  v_first BOOLEAN;

  PROCEDURE apply_column(
      p_first         IN BOOLEAN,
      p_object_name   IN VARCHAR2,
      p_policy_name   IN VARCHAR2,
      p_column_name   IN VARCHAR2,
      p_template_code IN VARCHAR2
  ) IS
    PROCEDURE first_policy(
        p_function_type IN BINARY_INTEGER,
        p_pattern       IN VARCHAR2 DEFAULT NULL,
        p_replace       IN VARCHAR2 DEFAULT NULL
    ) IS
    BEGIN
      DBMS_REDACT.ADD_POLICY(
        object_schema         => 'POC_2',
        object_name           => p_object_name,
        policy_name           => p_policy_name,
        policy_description    => 'Managed by CB masking rule backoffice',
        column_name           => p_column_name,
        function_type         => p_function_type,
        expression            => '1=1',
        regexp_pattern        => p_pattern,
        regexp_replace_string => p_replace,
        enable                => TRUE
      );
    END;

    PROCEDURE additional_column(
        p_function_type IN BINARY_INTEGER,
        p_pattern       IN VARCHAR2 DEFAULT NULL,
        p_replace       IN VARCHAR2 DEFAULT NULL
    ) IS
    BEGIN
      DBMS_REDACT.ALTER_POLICY(
        object_schema         => 'POC_2',
        object_name           => p_object_name,
        policy_name           => p_policy_name,
        action                => DBMS_REDACT.ADD_COLUMN,
        column_name           => p_column_name,
        function_type         => p_function_type,
        regexp_pattern        => p_pattern,
        regexp_replace_string => p_replace
      );
    END;

    PROCEDURE invoke(
        p_function_type IN BINARY_INTEGER,
        p_pattern       IN VARCHAR2 DEFAULT NULL,
        p_replace       IN VARCHAR2 DEFAULT NULL
    ) IS
    BEGIN
      IF p_first THEN
        first_policy(p_function_type, p_pattern, p_replace);
      ELSE
        additional_column(p_function_type, p_pattern, p_replace);
      END IF;
    END;
  BEGIN
    CASE p_template_code
      WHEN 'NULLIFY' THEN
        invoke(DBMS_REDACT.NULLIFY);
      WHEN 'FULL' THEN
        invoke(DBMS_REDACT.FULL);
      WHEN 'TEXT_PARTIAL' THEN
        invoke(DBMS_REDACT.REGEXP, '(^.).*$', '\1***');
      WHEN 'RRN_PARTIAL' THEN
        invoke(DBMS_REDACT.REGEXP, '(^[0-9]{6})-?[0-9]{7}$', '\1-*******');
      ELSE
        RAISE_APPLICATION_ERROR(-20011, 'Unsupported template: ' || p_template_code);
    END CASE;
  END;

  PROCEDURE drop_managed_policy(p_object_name IN VARCHAR2, p_policy_name IN VARCHAR2) IS
  BEGIN
    DBMS_REDACT.DROP_POLICY(
      object_schema => 'POC_2',
      object_name   => p_object_name,
      policy_name   => p_policy_name
    );
  EXCEPTION
    WHEN OTHERS THEN
      NULL;
  END;

  PROCEDURE drop_expression(p_expression_name IN VARCHAR2) IS
  BEGIN
    DBMS_REDACT.DROP_POLICY_EXPRESSION(p_expression_name);
  EXCEPTION
    WHEN OTHERS THEN
      NULL;
  END;

BEGIN
  -- These three policies are legacy test policies found on the KB objects.
  -- Data Redaction permits only one policy per object, so replace them with
  -- the managed policy rather than leaving their client-identifier tests live.
  drop_managed_policy('KB_CUSTOMERS', 'KB_CUSTOMER_PII_REDACT');
  drop_managed_policy('KB_CLAIMS', 'KB_CLAIM_AMOUNT_REDACT');
  drop_managed_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_HOLDING_REDACT');

  FOR object_row IN (
    SELECT DISTINCT po.object_name
    FROM cb_column_masking_rule link
    JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
    JOIN cb_protected_column pc ON pc.column_id = link.column_id
    JOIN cb_protected_object po ON po.object_id = pc.object_id
    WHERE rule.enabled_yn = 'Y'
      AND po.owner = 'POC_2'
  ) LOOP
    v_policy_name := 'CB_ASO_' || object_row.object_name || '_MASK';
    drop_managed_policy(object_row.object_name, v_policy_name);
  END LOOP;

  FOR column_row IN (
    SELECT link.column_id
    FROM cb_column_masking_rule link
  ) LOOP
    drop_expression('CBMR_' || TO_CHAR(column_row.column_id));
  END LOOP;

  FOR object_row IN (
    SELECT DISTINCT po.object_name
    FROM cb_column_masking_rule link
    JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
    JOIN cb_protected_column pc ON pc.column_id = link.column_id
    JOIN cb_protected_object po ON po.object_id = pc.object_id
    WHERE rule.enabled_yn = 'Y'
      AND po.owner = 'POC_2'
    ORDER BY po.object_name
  ) LOOP
    v_policy_name := 'CB_ASO_' || object_row.object_name || '_MASK';
    v_first := TRUE;
    FOR column_row IN (
      SELECT pc.column_id,
             pc.column_name,
             rule.template_code,
             rule.rule_name
      FROM cb_column_masking_rule link
      JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
      JOIN cb_protected_column pc ON pc.column_id = link.column_id
      JOIN cb_protected_object po ON po.object_id = pc.object_id
      WHERE po.owner = 'POC_2'
        AND po.object_name = object_row.object_name
        AND rule.enabled_yn = 'Y'
      ORDER BY pc.column_id
    ) LOOP
      apply_column(v_first, object_row.object_name, v_policy_name,
          column_row.column_name, column_row.template_code);
      v_expression_name := 'CBMR_' || TO_CHAR(column_row.column_id);
      DBMS_REDACT.CREATE_POLICY_EXPRESSION(
        policy_expression_name        => v_expression_name,
        expression                    => 'SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(column_row.column_id)
            || ''') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(column_row.column_id) || ''') <> ''Y''',
        policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
      );
      DBMS_REDACT.APPLY_POLICY_EXPR_TO_COL(
        object_schema          => 'POC_2',
        object_name            => object_row.object_name,
        column_name            => column_row.column_name,
        policy_expression_name => v_expression_name
      );
      v_first := FALSE;
    END LOOP;
  END LOOP;
END;
/
COMMIT;

PROMPT === Verification ===
SELECT object_name, policy_name, expression
FROM redaction_policies
WHERE object_owner = 'POC_2'
  AND policy_name LIKE 'CB_ASO_%'
ORDER BY object_name, policy_name;

SELECT object_name, column_name, function_type
FROM redaction_columns
WHERE object_owner = 'POC_2'
  AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
ORDER BY object_name, column_name;

SELECT user_row.user_name,
       protected_object.object_name,
       protected_column.column_name,
       user_rule.decision
FROM cb_user_masking_rule user_rule
JOIN cb_app_user user_row ON user_row.user_id = user_rule.user_id
JOIN cb_protected_column protected_column ON protected_column.column_id = user_rule.column_id
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
ORDER BY user_row.user_name, protected_object.object_name, protected_column.column_name;
EXIT
