KB-34E3 rev 4

23-P3A — IU Gateway Allow-list Patch — Execution Prompt (rev4)

20 min read Revision 4
pack-23p3apromptrev4gatewayallow-listexecution

23-P3A — IU Gateway Allow-list Patch — Execution Prompt (rev4)

Date: 2026-05-06 Author: Opus (Claude) Status: PROMPT rev4 — chờ GPT/User final review. CHƯA dispatch. Rev4: 8 fixes (named params, idempotent, exact unique, PUBLIC check, T8 fuller, T5B blocker, row-leak, verdict SKIPPED).


Mục tiêu

Patch fn_iu_gateway_write_guard() exact-match → allow-list. Allow-list: fn_iu_create, fn_iu_apply_edit_draft. fn_iu_apply_edit_draft chưa tồn tại — P3A chỉ mở cổng tương lai. Report: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3a-iu-gateway-allow-list-patch-report.md


Setup

#!/usr/bin/env bash
CONTAINER="postgres"
DB="directus"
DBUSER="directus"
PSQL=(docker exec -i "$CONTAINER" psql -U "$DBUSER" -d "$DB" -v ON_ERROR_STOP=1)
PSQL_NOSTOP=(docker exec -i "$CONTAINER" psql -U "$DBUSER" -d "$DB")
TS=$(date +%Y%m%d-%H%M%S)
LOG="/tmp/23-p3a.${TS}.log"
exec > >(tee -a "$LOG") 2>&1

PREFLIGHT_STATUS=""
PATCH_STATUS="NOT_RUN"
PATCH_EXIT=""
TEST_FAIL=0
PHASE_STATUS=""
P3B_READINESS="BLOCKED"
ROLLBACK_STATUS="N/A"
SOURCE_HASH_BEFORE=""
SOURCE_HASH_AFTER=""
GUARD_OID=""
FN_OWNER=""
FN_SECDEF=""
FN_ACL_BEFORE=""
FN_ACL_AFTER=""
FUNC_DEF_BEFORE=""
ALLOW_KEY_EXISTED_BEFORE="false"
ALLOW_KEY_VALUE_BEFORE=""
T1="NOT_RUN"; T2="NOT_RUN"; T3="NOT_RUN"; T4="NOT_RUN"
T5="NOT_RUN"; T5B="NOT_RUN"; T6="NOT_RUN"; T7="NOT_RUN"
T8="NOT_RUN"; T9="NOT_RUN"; T10="NOT_RUN"; T11="NOT_RUN"
PILOT_ADDR=""
IU_COUNT_BEFORE=""; UV_COUNT_BEFORE=""
IU_COUNT_AFTER=""; UV_COUNT_AFTER=""
EXISTING_UV_ID=""
ALREADY_PATCHED="false"

echo "=== P3A START $TS ==="

§1. Preflight

echo "=== PREFLIGHT ==="

# Gate 1: Guard exists once, public, zero args
GUARD_COUNT=$("${PSQL[@]}" -t -c "
  SELECT count(*) FROM pg_proc p
  JOIN pg_namespace n ON p.pronamespace = n.oid
  WHERE p.proname='fn_iu_gateway_write_guard' AND n.nspname='public' AND p.pronargs=0;
" | tr -d ' ')
echo "PREFLIGHT_GUARD_COUNT=$GUARD_COUNT"
[ "$GUARD_COUNT" = "1" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: guard_count=$GUARD_COUNT"; }

# Gate 2: Capture full definition + metadata
if [ -z "$PREFLIGHT_STATUS" ]; then
  GUARD_OID=$("${PSQL[@]}" -t -c "
    SELECT p.oid FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid
    WHERE p.proname='fn_iu_gateway_write_guard' AND n.nspname='public' AND p.pronargs=0;
  " | tr -d ' ')
  FUNC_DEF_BEFORE=$("${PSQL[@]}" -t -A -c "SELECT pg_get_functiondef($GUARD_OID);")
  SOURCE_HASH_BEFORE=$("${PSQL[@]}" -t -c "SELECT md5(prosrc) FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
  FN_OWNER=$("${PSQL[@]}" -t -c "SELECT proowner::regrole FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
  FN_SECDEF=$("${PSQL[@]}" -t -c "SELECT prosecdef FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
  FN_ACL_BEFORE=$("${PSQL[@]}" -t -A -c "SELECT proacl FROM pg_proc WHERE oid=$GUARD_OID;")
  echo "$FUNC_DEF_BEFORE" > "/tmp/p3a-guard-backup-${TS}.sql"
  echo "PREFLIGHT_HASH=$SOURCE_HASH_BEFORE OWNER=$FN_OWNER SECDEF=$FN_SECDEF"
fi

# Gate 3: Marker config
if [ -z "$PREFLIGHT_STATUS" ]; then
  MARKER_KEY=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_create.gateway.marker_key';")
  MARKER_VALUE=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_create.gateway.marker_value';")
  if [ "$MARKER_KEY" != "app.canonical_writer" ] || [ "$MARKER_VALUE" != "fn_iu_create" ]; then
    PREFLIGHT_STATUS="FAIL"; echo "FAIL: marker_key=$MARKER_KEY marker_value=$MARKER_VALUE"
  fi
fi

# Gate 4: Triggers
if [ -z "$PREFLIGHT_STATUS" ]; then
  TRIGGER_COUNT=$("${PSQL[@]}" -t -c "
    SELECT count(*) FROM pg_trigger
    WHERE tgname IN ('trg_aa_iu_gateway_write_guard','trg_aa_uv_gateway_write_guard')
      AND tgenabled IN ('O','A');
  " | tr -d ' ')
  [ "$TRIGGER_COUNT" = "2" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: triggers=$TRIGGER_COUNT"; }
fi

# Gate 5: Allow-list key + idempotent check
if [ -z "$PREFLIGHT_STATUS" ]; then
  ALLOW_KEY_CHECK=$("${PSQL_NOSTOP[@]}" -t -A -c "
    SELECT value FROM dot_config WHERE key='iu_create.gateway.allowed_marker_values';
  " 2>/dev/null)
  if [ -n "$ALLOW_KEY_CHECK" ]; then
    ALLOW_KEY_EXISTED_BEFORE="true"
    ALLOW_KEY_VALUE_BEFORE="$ALLOW_KEY_CHECK"
    if [ "$ALLOW_KEY_CHECK" = "fn_iu_create,fn_iu_apply_edit_draft" ]; then
      echo "PREFLIGHT_ALLOW_LIST=already_seeded_exact"
    else
      PREFLIGHT_STATUS="FAIL"; echo "FAIL: incompatible allow-list=$ALLOW_KEY_CHECK"
    fi
  fi
fi

# Gate 6: No duplicate gateway keys
if [ -z "$PREFLIGHT_STATUS" ]; then
  DUP=$("${PSQL[@]}" -t -c "
    SELECT count(*) FROM (SELECT key FROM dot_config WHERE key LIKE 'iu_create.gateway.%' GROUP BY key HAVING count(*)>1) d;
  " | tr -d ' ')
  [ "$DUP" = "0" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: dup_keys=$DUP"; }
fi

# Gate 7: Guard already patched? (idempotent logic)
if [ -z "$PREFLIGHT_STATUS" ]; then
  HAS_ALLOW_LOGIC=$("${PSQL[@]}" -t -c "
    SELECT prosrc LIKE '%allowed_marker_values%' FROM pg_proc WHERE oid=$GUARD_OID;
  " | tr -d ' ')
  if [ "$HAS_ALLOW_LOGIC" = "t" ]; then
    if [ "$ALLOW_KEY_CHECK" = "fn_iu_create,fn_iu_apply_edit_draft" ]; then
      ALREADY_PATCHED="true"
      echo "PREFLIGHT: guard already patched + key exact match → SKIPPED_ALREADY_PATCHED"
    else
      PREFLIGHT_STATUS="FAIL"; echo "FAIL: guard patched but key mismatch"
    fi
  fi
fi

# Gate 8: dot_config unique constraint exactly on column 'key'
if [ -z "$PREFLIGHT_STATUS" ]; then
  KEY_UNIQUE=$("${PSQL[@]}" -t -c "
    SELECT count(*) FROM pg_index i
    WHERE i.indrelid = 'dot_config'::regclass
      AND i.indisunique = true
      AND i.indnatts = 1
      AND (SELECT a.attname FROM pg_attribute a
           WHERE a.attrelid = i.indrelid AND a.attnum = i.indkey[0]) = 'key';
  " | tr -d ' ')
  echo "PREFLIGHT_KEY_UNIQUE=$KEY_UNIQUE"
  [ "$KEY_UNIQUE" != "0" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: no single-column unique on dot_config.key"; }
fi

# Gate 9: Discover fn_iu_create signature + assert required params
if [ -z "$PREFLIGHT_STATUS" ]; then
  FN_CREATE_SIG=$("${PSQL[@]}" -t -A -c "
    SELECT p.oid::regprocedure::text FROM pg_proc p
    JOIN pg_namespace n ON n.oid=p.pronamespace
    WHERE n.nspname='public' AND p.proname='fn_iu_create';
  ")
  FN_CREATE_ARGS=$("${PSQL[@]}" -t -A -c "
    SELECT pg_get_function_arguments(p.oid) FROM pg_proc p
    JOIN pg_namespace n ON n.oid=p.pronamespace
    WHERE n.nspname='public' AND p.proname='fn_iu_create';
  ")
  echo "PREFLIGHT_FN_CREATE_SIG=$FN_CREATE_SIG"
  echo "PREFLIGHT_FN_CREATE_ARGS=$FN_CREATE_ARGS"
  # Assert 4 required named params exist
  for PARAM in p_canonical_address p_title p_body p_actor; do
    if ! echo "$FN_CREATE_ARGS" | grep -q "$PARAM"; then
      PREFLIGHT_STATUS="FAIL"; echo "FAIL: fn_iu_create missing param $PARAM"
    fi
  done
fi

# Gate 10: Counts + existing UV for update test
if [ -z "$PREFLIGHT_STATUS" ]; then
  IU_COUNT_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
  UV_COUNT_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
  EXISTING_UV_ID=$("${PSQL[@]}" -t -A -c "SELECT id FROM unit_version LIMIT 1;")
  echo "IU_COUNT_BEFORE=$IU_COUNT_BEFORE UV_COUNT_BEFORE=$UV_COUNT_BEFORE EXISTING_UV_ID=$EXISTING_UV_ID"
fi

# Final preflight
if [ -z "$PREFLIGHT_STATUS" ]; then PREFLIGHT_STATUS="PASS"; fi
echo "PREFLIGHT_STATUS=$PREFLIGHT_STATUS ALREADY_PATCHED=$ALREADY_PATCHED"

if [ "$PREFLIGHT_STATUS" != "PASS" ]; then
  PATCH_STATUS="NOT_RUN"; PHASE_STATUS="FAIL"; P3B_READINESS="BLOCKED"
  echo "PREFLIGHT FAILED. Skip patch/tests."
fi

§2. Patch (skip if already patched)

if [ "$PREFLIGHT_STATUS" = "PASS" ] && [ "$ALREADY_PATCHED" = "true" ]; then
  PATCH_STATUS="SKIPPED_ALREADY_PATCHED"
  SOURCE_HASH_AFTER=$SOURCE_HASH_BEFORE
  echo "PATCH_STATUS=SKIPPED_ALREADY_PATCHED"
fi

if [ "$PREFLIGHT_STATUS" = "PASS" ] && [ "$ALREADY_PATCHED" = "false" ]; then
echo "=== PATCH ==="

PATCH_EXIT=0
"${PSQL[@]}" <<'PATCH_SQL' || PATCH_EXIT=$?
BEGIN;

INSERT INTO dot_config (key, value)
VALUES ('iu_create.gateway.allowed_marker_values', 'fn_iu_create,fn_iu_apply_edit_draft')
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;

CREATE OR REPLACE FUNCTION public.fn_iu_gateway_write_guard()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog, public
AS $fn$
DECLARE
  v_marker_key text;
  v_marker_value text;
  v_current text;
  v_allowed_csv text;
  v_allowed text[];
BEGIN
  SELECT value INTO v_marker_key
    FROM public.dot_config WHERE key = 'iu_create.gateway.marker_key';
  SELECT value INTO v_marker_value
    FROM public.dot_config WHERE key = 'iu_create.gateway.marker_value';

  IF v_marker_key IS NULL THEN
    RETURN NEW;
  END IF;

  v_current := current_setting(v_marker_key, true);

  SELECT value INTO v_allowed_csv
    FROM public.dot_config
    WHERE key = 'iu_create.gateway.allowed_marker_values';

  IF v_allowed_csv IS NOT NULL AND v_allowed_csv <> '' THEN
    SELECT array_agg(btrim(elem))
      INTO v_allowed
      FROM unnest(string_to_array(v_allowed_csv, ',')) AS elem
      WHERE btrim(elem) <> '';

    IF v_allowed IS NOT NULL AND v_current = ANY(v_allowed) THEN
      RETURN NEW;
    END IF;
  ELSE
    IF v_current IS NOT NULL AND v_current = v_marker_value THEN
      RETURN NEW;
    END IF;
  END IF;

  RAISE EXCEPTION
    'IU Gateway blocked: direct write to % not allowed. '
    'Use canonical functions (fn_iu_create, fn_iu_apply_edit_draft). '
    'See README: %',
    TG_TABLE_NAME,
    COALESCE(
      (SELECT value FROM public.dot_config WHERE key = 'iu_create.gateway.readme_path'),
      'knowledge/dev/laws/dieu44-trien-khai/readme/iu-create-gateway-readme.md'
    );
END;
$fn$;

REVOKE ALL ON FUNCTION public.fn_iu_gateway_write_guard() FROM PUBLIC;

COMMIT;
PATCH_SQL

echo "PATCH_EXIT=$PATCH_EXIT"
if [ "$PATCH_EXIT" != "0" ]; then
  PATCH_STATUS="FAIL"; PHASE_STATUS="FAIL"
else
  PATCH_STATUS="OK"
  SOURCE_HASH_AFTER=$("${PSQL[@]}" -t -c "SELECT md5(prosrc) FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
  FN_ACL_AFTER=$("${PSQL[@]}" -t -A -c "SELECT proacl FROM pg_proc WHERE oid=$GUARD_OID;")
  echo "SOURCE_HASH_AFTER=$SOURCE_HASH_AFTER"
fi

fi # patch needed

§3. Tests (run for both OK and SKIPPED_ALREADY_PATCHED)

if [ "$PATCH_STATUS" = "OK" ] || [ "$PATCH_STATUS" = "SKIPPED_ALREADY_PATCHED" ]; then
echo "=== TESTS ==="

# If skipped, capture current metadata for T10
if [ "$PATCH_STATUS" = "SKIPPED_ALREADY_PATCHED" ]; then
  FN_ACL_AFTER=$FN_ACL_BEFORE
fi

# --- T1: Source has allow-list logic ---
T1_RAW=$("${PSQL[@]}" -t -c "SELECT prosrc LIKE '%allowed_marker_values%' FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
[ "$T1_RAW" = "t" ] && T1="PASS" || { T1="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T1=$T1"

# --- T2: dot_config key correct ---
T2_RAW=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_create.gateway.allowed_marker_values';")
[ "$T2_RAW" = "fn_iu_create,fn_iu_apply_edit_draft" ] && T2="PASS" || { T2="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T2=$T2 value=$T2_RAW"

# --- T3: fn_iu_create REAL (named params, retain pilot) ---
PILOT_ADDR="test/p3a/gateway-verify-${TS}"
T3_RAW=$("${PSQL[@]}" -t -A -c "
  WITH r AS (
    SELECT public.fn_iu_create(
      p_canonical_address => '${PILOT_ADDR}',
      p_title             => 'P3A gateway verify pilot',
      p_body              => 'Body: fn_iu_create after allow-list patch.',
      p_actor             => 'agent:p3a-test'
    ) AS j
  )
  SELECT j->>'status' FROM r;
")
echo "T3_STATUS=$T3_RAW PILOT=$PILOT_ADDR"
if [ "$T3_RAW" = "created" ] || [ "$T3_RAW" = "exists_complete" ]; then T3="PASS"; else T3="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); fi
echo "T3=$T3"
IU_COUNT_AFTER=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
UV_COUNT_AFTER=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
echo "IU_AFTER=$IU_COUNT_AFTER UV_AFTER=$UV_COUNT_AFTER"

# --- T4: Direct IU INSERT blocked ---
T4_OUTPUT=$("${PSQL_NOSTOP[@]}" -c "
  INSERT INTO information_unit (id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile)
  VALUES (gen_random_uuid(),'test/p3a/direct-block-${TS}','test','draft','test','{}');
" 2>&1) || true
if echo "$T4_OUTPUT" | grep -q "IU Gateway blocked"; then T4="PASS"; else T4="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); fi
echo "T4=$T4"

# --- T5: Direct UV INSERT ---
T5_OUTPUT=$("${PSQL_NOSTOP[@]}" -c "
  INSERT INTO unit_version (id,unit_id,body,content_hash,version_seq,lifecycle_status,created_by)
  VALUES (gen_random_uuid(),gen_random_uuid(),'t','t',999,'draft','t');
" 2>&1) || true
if echo "$T5_OUTPUT" | grep -q "IU Gateway blocked"; then
  T5="PASS_GATEWAY_BLOCKED"
elif echo "$T5_OUTPUT" | grep -q "violates foreign key"; then
  T5="PASS_WRITE_PREVENTED_FK_FIRST"
else
  T5="FAIL"; TEST_FAIL=$((TEST_FAIL+1))
fi
echo "T5=$T5"

# --- T5B: Direct UV UPDATE blocked (proves UPDATE guard) ---
if [ -n "$EXISTING_UV_ID" ]; then
  T5B_OUTPUT=$("${PSQL_NOSTOP[@]}" <<EOF 2>&1) || true
BEGIN;
UPDATE unit_version SET body = body || ' p3a-test' WHERE id = '${EXISTING_UV_ID}';
ROLLBACK;
EOF
  if echo "$T5B_OUTPUT" | grep -q "IU Gateway blocked"; then
    T5B="PASS_GATEWAY_BLOCKED_UPDATE"
  else
    T5B="FAIL"; TEST_FAIL=$((TEST_FAIL+1))
  fi
else
  T5B="FAIL_NO_EXISTING_UV"
  TEST_FAIL=$((TEST_FAIL+1))
fi
echo "T5B=$T5B"

# --- T6: fn_iu_apply_edit_draft marker accepted ---
T6_OUTPUT=$("${PSQL_NOSTOP[@]}" <<'T6SQL' 2>&1) || true
BEGIN;
SELECT set_config('app.canonical_writer', 'fn_iu_apply_edit_draft', true);
INSERT INTO information_unit (id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile)
VALUES (gen_random_uuid(),'test/p3a/apply-marker-ROLLBACK','test','draft','test','{"title":"t6"}');
ROLLBACK;
T6SQL
if echo "$T6_OUTPUT" | grep -q "IU Gateway blocked"; then
  T6="FAIL_REJECTED_BY_GATEWAY"; TEST_FAIL=$((TEST_FAIL+1))
elif echo "$T6_OUTPUT" | grep -q "ROLLBACK"; then
  T6="PASS_GATEWAY_ACCEPTED_INSERT_ROLLED_BACK"
elif echo "$T6_OUTPUT" | grep -q "ERROR"; then
  T6="PASS_GATEWAY_ACCEPTED_BUT_OTHER_ERROR"
else
  T6="PASS_GATEWAY_ACCEPTED_INSERT_ROLLED_BACK"
fi
echo "T6=$T6"
"${PSQL_NOSTOP[@]}" -c "SELECT set_config('app.canonical_writer','',true);" >/dev/null 2>&1

# --- T7: Unknown marker blocked ---
T7_OUTPUT=$("${PSQL_NOSTOP[@]}" <<'T7SQL' 2>&1) || true
BEGIN;
SELECT set_config('app.canonical_writer', 'fn_unknown_bad_actor', true);
INSERT INTO information_unit (id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile)
VALUES (gen_random_uuid(),'test/p3a/unknown-block-ROLLBACK','test','draft','test','{}');
ROLLBACK;
T7SQL
if echo "$T7_OUTPUT" | grep -q "IU Gateway blocked"; then T7="PASS"; else T7="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); fi
echo "T7=$T7"
"${PSQL_NOSTOP[@]}" -c "SELECT set_config('app.canonical_writer','',true);" >/dev/null 2>&1

# --- T8: Error message guidance ---
T8="PASS"
if ! echo "$T4_OUTPUT" | grep -q "IU Gateway blocked"; then T8="FAIL_NO_GATEWAY_MSG"; TEST_FAIL=$((TEST_FAIL+1)); fi
if ! echo "$T4_OUTPUT" | grep -q "fn_iu_create"; then T8="FAIL_NO_CANONICAL_FN"; TEST_FAIL=$((TEST_FAIL+1)); fi
if ! echo "$T4_OUTPUT" | grep -qi "readme"; then
  T8="WARN_NO_README_IN_MSG"  # non-critical
fi
echo "T8=$T8"

# --- T9: Triggers still enabled ---
T9_RAW=$("${PSQL[@]}" -t -c "
  SELECT count(*) FROM pg_trigger
  WHERE tgname IN ('trg_aa_iu_gateway_write_guard','trg_aa_uv_gateway_write_guard')
    AND tgenabled IN ('O','A');
" | tr -d ' ')
[ "$T9_RAW" = "2" ] && T9="PASS" || { T9="FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T9=$T9"

# --- T10: Metadata preserved ---
T10="PASS"
FN_OWNER_AFTER=$("${PSQL[@]}" -t -c "SELECT proowner::regrole FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
FN_SECDEF_AFTER=$("${PSQL[@]}" -t -c "SELECT prosecdef FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
FN_CONFIG_AFTER=$("${PSQL[@]}" -t -A -c "SELECT proconfig FROM pg_proc WHERE oid=$GUARD_OID;")
[ "$FN_OWNER_AFTER" = "$FN_OWNER" ] || { T10="FAIL_OWNER"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$FN_SECDEF_AFTER" = "t" ] || { T10="FAIL_SECDEF"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "$FN_CONFIG_AFTER" | grep -q "pg_catalog" || { T10="FAIL_SEARCHPATH"; TEST_FAIL=$((TEST_FAIL+1)); }
# PUBLIC not broadened: check via aclexplode or routine_privileges
PUBLIC_GRANT=$("${PSQL_NOSTOP[@]}" -t -c "
  SELECT count(*) FROM (
    SELECT (aclexplode(proacl)).grantee AS gid,
           (aclexplode(proacl)).privilege_type AS priv
    FROM pg_proc WHERE oid=$GUARD_OID
  ) x WHERE x.gid = 0 AND x.priv = 'EXECUTE';
" 2>/dev/null | tr -d ' ')
# grantee=0 is PUBLIC in aclexplode
if [ "$PUBLIC_GRANT" != "" ] && [ "$PUBLIC_GRANT" != "0" ]; then
  T10="FAIL_PUBLIC_BROADENED"; TEST_FAIL=$((TEST_FAIL+1))
fi
echo "T10=$T10 owner=$FN_OWNER_AFTER secdef=$FN_SECDEF_AFTER public_grant=$PUBLIC_GRANT"

# --- T11: Row-leak check (T6/T7 rollback rows must not exist) ---
LEAK_COUNT=$("${PSQL[@]}" -t -c "
  SELECT count(*) FROM information_unit
  WHERE canonical_address IN ('test/p3a/apply-marker-ROLLBACK','test/p3a/unknown-block-ROLLBACK');
" | tr -d ' ')
if [ "$LEAK_COUNT" = "0" ]; then T11="PASS"; else T11="FAIL_ROW_LEAK"; TEST_FAIL=$((TEST_FAIL+1)); fi
echo "T11_ROW_LEAK=$T11 count=$LEAK_COUNT"

echo "TEST_FAIL_COUNT=$TEST_FAIL"
fi # tests

§4. Rollback (only if patch ran AND tests failed)

if [ "$TEST_FAIL" != "0" ] && [ "$PATCH_STATUS" = "OK" ]; then
  echo "=== ROLLBACK ==="
  ROLLBACK_STATUS="ATTEMPTING"
  RESTORE_FN=1; RESTORE_KEY=1

  if [ -f "/tmp/p3a-guard-backup-${TS}.sql" ]; then
    "${PSQL_NOSTOP[@]}" -f "/tmp/p3a-guard-backup-${TS}.sql" 2>&1 && RESTORE_FN=0
  fi

  if [ "$ALLOW_KEY_EXISTED_BEFORE" = "true" ]; then
    "${PSQL_NOSTOP[@]}" -c "UPDATE dot_config SET value='${ALLOW_KEY_VALUE_BEFORE}' WHERE key='iu_create.gateway.allowed_marker_values';" 2>&1 && RESTORE_KEY=0
  else
    "${PSQL_NOSTOP[@]}" -c "DELETE FROM dot_config WHERE key='iu_create.gateway.allowed_marker_values';" 2>&1 && RESTORE_KEY=0
  fi

  RESTORED_HASH=$("${PSQL[@]}" -t -c "SELECT md5(prosrc) FROM pg_proc WHERE oid=$GUARD_OID;" | tr -d ' ')
  if [ "$RESTORED_HASH" = "$SOURCE_HASH_BEFORE" ] && [ "$RESTORE_FN" = "0" ] && [ "$RESTORE_KEY" = "0" ]; then
    ROLLBACK_STATUS="RESTORED"; PHASE_STATUS="FAIL"
  else
    ROLLBACK_STATUS="CRITICAL"; PHASE_STATUS="CRITICAL"
  fi
  echo "ROLLBACK_STATUS=$ROLLBACK_STATUS"
fi

§5. Report

echo "=== FINAL VERDICT ==="
if [ "$PREFLIGHT_STATUS" != "PASS" ]; then
  PHASE_STATUS="FAIL"; P3B_READINESS="BLOCKED"
elif [ "$PATCH_STATUS" = "FAIL" ]; then
  PHASE_STATUS="FAIL"; P3B_READINESS="BLOCKED"
elif [ "$TEST_FAIL" = "0" ]; then
  PHASE_STATUS="PASS"; P3B_READINESS="READY"
fi

echo "phase_status=$PHASE_STATUS"
echo "p3b_readiness=$P3B_READINESS"
echo "patch_status=$PATCH_STATUS"
echo "preflight_status=$PREFLIGHT_STATUS"
echo "source_hash_before=$SOURCE_HASH_BEFORE"
echo "source_hash_after=$SOURCE_HASH_AFTER"
echo "rollback_status=$ROLLBACK_STATUS"
echo "test_fail_count=$TEST_FAIL"
echo "T1=$T1 T2=$T2 T3=$T3 T4=$T4 T5=$T5 T5B=$T5B T6=$T6 T7=$T7 T8=$T8 T9=$T9 T10=$T10 T11=$T11"
echo "pilot=$PILOT_ADDR iu=$IU_COUNT_BEFORE→$IU_COUNT_AFTER uv=$UV_COUNT_BEFORE→$UV_COUNT_AFTER"
echo "LOG=$LOG"

echo ""
echo "=== AGENT: UPLOAD REPORT NOW ==="
echo "Path: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3a-iu-gateway-allow-list-patch-report.md"
echo "Include: full verdict, all T1-T11, hash before/after, pilot address, counts, log path."
echo "Upload even on FAIL/CRITICAL."

Hard Boundaries

  • ❌ No table DDL
  • ❌ No new functions
  • ❌ No trigger changes
  • ❌ No vector mutation
  • ❌ No cleanup
  • ❌ No retry / No improvise

23-P3A Prompt rev4 | 2026-05-06 | 8 fixes | Chờ GPT/User final review