KB-2D62 rev 3

23-P3C3 — IU Natural Save Router — Execution Prompt (rev3)

23 min read Revision 3

23-P3C3 — IU Natural Save Router — Execution Prompt (rev3)

Date: 2026-05-07 Status: PROMPT rev3 — GPT 6 evidence fixes. Chờ final review. CHƯA dispatch. Report: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c3-iu-natural-save-router-report.md Rev2→Rev3: T13 draft/comment deltas, preflight all 9 protected functions, T12 count=9, T9 LATERAL, pilot IDs, cleanup unconditional.


Purpose

Create fn_iu_save — single AI front-door for content create/edit.


Hard Boundaries

  • ❌ No table DDL / trigger / gateway changes
  • ❌ No direct IU/UV INSERT/UPDATE/DELETE inside fn_iu_save
  • ❌ No app.canonical_writer marker inside fn_iu_save
  • ❌ No alter P3C1/P3C2/P3B-FU functions (T12)
  • ❌ No vector mutation / notification / cleanup
  • ❌ No comment approval
  • ❌ No policy switch (T14)
  • ❌ fn_iu_save already exists → STOP
  • ✅ fn_iu_save delegates ALL writes to existing functions

Setup

#!/usr/bin/env bash
CONTAINER="${PG_CONTAINER:-postgres}"
DB="${PG_DB:-directus}"
DBUSER="${PG_USER:-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-p3c3.${TS}.log"
exec > >(tee -a "$LOG") 2>&1
PREFLIGHT_STATUS=""; FN_STATUS="NOT_RUN"; TEST_FAIL=0
PHASE_STATUS=""; FN_OWNER=""; FN_GRANTEES=""
IU_BEFORE=""; UV_BEFORE=""; DRAFT_BEFORE=""; COMMENT_BEFORE=""
IU_NOW=""; UV_NOW=""; DRAFT_NOW=""; COMMENT_NOW=""
P3C_HASHES_BEFORE=""; P3C_HASHES_AFTER=""
PROTECTED_COUNT_BEFORE=""; PROTECTED_COUNT_AFTER=""
POLICY_BEFORE=""; TEST_NEW_ADDR=""
T1_S=""; T1_UID=""; T1_VID=""; T1_FULL=""
T2_S=""; T2_DID=""
T3_S=""; T3_DID=""; T3_VID=""
T7=""
US=$'\x1f'
echo "=== P3C3 START $TS ==="

§1. Preflight

echo "=== PREFLIGHT ==="

# rev3-F2: Verify ALL protected lower-layer functions exact signatures
PROTECTED_SIGS=(
  "public.fn_iu_create(text,text,text,text)"
  "public.fn_iu_create_edit_draft(text,text,text,text,text)"
  "public.fn_iu_edit_plan(text,text,text,text)"
  "public.fn_iu_comment(text,text,text,text,text,jsonb)"
  "public.fn_iu_apply_edit_draft(uuid,text,text)"
  "public.fn_iu_edit(text,text,text,text,text,text)"
  "public.fn_iu_verify_invariants(text)"
  "public.fn_content_hash(text)"
)
for SIG in "${PROTECTED_SIGS[@]}"; do
  R=$("${PSQL[@]}" -t -A -c "SELECT to_regprocedure('$SIG');")
  [ -n "$R" ] && [ "$R" != "-" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: $SIG missing"; break; }
done

# fn_iu_comment_edit_draft (exact sig may vary — verify at least exists)
if [ -z "$PREFLIGHT_STATUS" ]; then
  CED=$("${PSQL[@]}" -t -c "SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid WHERE n.nspname='public' AND p.proname='fn_iu_comment_edit_draft';" | tr -d ' ')
  [ "$CED" -ge "1" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: fn_iu_comment_edit_draft missing"; }
fi

# fn_iu_save must NOT exist
if [ -z "$PREFLIGHT_STATUS" ]; then
  SAVE_EXISTS=$("${PSQL[@]}" -t -c "SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid WHERE n.nspname='public' AND p.proname='fn_iu_save';" | tr -d ' ')
  [ "$SAVE_EXISTS" = "0" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: fn_iu_save already exists"; }
fi

# Schema tables
if [ -z "$PREFLIGHT_STATUS" ]; then
  for TBL in unit_edit_draft unit_edit_comment information_unit unit_version dot_config; do
    E=$("${PSQL[@]}" -t -c "SELECT count(*) FROM pg_tables WHERE schemaname='public' AND tablename='$TBL';" | tr -d ' ')
    [ "$E" = "1" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: $TBL missing"; }
  done
fi

# Policy
if [ -z "$PREFLIGHT_STATUS" ]; then
  POLICY_BEFORE=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_edit.policy.default_mode';")
  echo "POLICY=$POLICY_BEFORE"
  [ -n "$POLICY_BEFORE" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: policy missing"; }
fi

# Gateway
if [ -z "$PREFLIGHT_STATUS" ]; then
  GW=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_create.gateway.allowed_marker_values';")
  echo "$GW" | grep -q "fn_iu_apply_edit_draft" || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: gateway"; }
fi

# Owner/grantees
if [ -z "$PREFLIGHT_STATUS" ]; then
  FN_OWNER=$("${PSQL[@]}" -t -A -c "SELECT proowner::regrole FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid WHERE n.nspname='public' AND p.proname='fn_iu_apply_edit_draft';")
  FN_GRANTEES=$("${PSQL_NOSTOP[@]}" -t -A -c "SELECT string_agg(DISTINCT x.grantee::regrole::text,',') FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid LEFT JOIN LATERAL aclexplode(p.proacl) x ON true WHERE n.nspname='public' AND p.proname='fn_iu_apply_edit_draft' AND x.privilege_type='EXECUTE' AND x.grantee!=0;" 2>/dev/null)
  [ -z "$FN_GRANTEES" ] && FN_GRANTEES="$FN_OWNER"
  echo "OWNER=$FN_OWNER GRANTEES=$FN_GRANTEES"
  [ -n "$FN_OWNER" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: owner"; }
fi

# rev3-F3: Function hashes + count (9 protected functions)
if [ -z "$PREFLIGHT_STATUS" ]; then
  P3C_HASHES_BEFORE=$("${PSQL[@]}" -t -A -c "
    SELECT string_agg(p.proname||'='||md5(p.prosrc),'|' ORDER BY p.proname)
    FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid
    WHERE n.nspname='public' AND p.proname IN
      ('fn_iu_edit_plan','fn_iu_create_edit_draft','fn_iu_comment_edit_draft','fn_iu_comment',
       'fn_iu_apply_edit_draft','fn_iu_edit','fn_iu_create','fn_content_hash','fn_iu_verify_invariants');")
  PROTECTED_COUNT_BEFORE=$("${PSQL[@]}" -t -c "
    SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid
    WHERE n.nspname='public' AND p.proname IN
      ('fn_iu_edit_plan','fn_iu_create_edit_draft','fn_iu_comment_edit_draft','fn_iu_comment',
       'fn_iu_apply_edit_draft','fn_iu_edit','fn_iu_create','fn_content_hash','fn_iu_verify_invariants');" | tr -d ' ')
  echo "PROTECTED_COUNT=$PROTECTED_COUNT_BEFORE"
  [ "$PROTECTED_COUNT_BEFORE" = "9" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: protected count=$PROTECTED_COUNT_BEFORE exp=9"; }
fi

# Test address + counts
if [ -z "$PREFLIGHT_STATUS" ]; then
  TEST_NEW_ADDR="test/p3c3/pilot-${TS}"
  echo "TEST_NEW=$TEST_NEW_ADDR"
  IU_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
  UV_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
  DRAFT_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_draft;" | tr -d ' ')
  COMMENT_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
  echo "IU=$IU_BEFORE UV=$UV_BEFORE D=$DRAFT_BEFORE C=$COMMENT_BEFORE"
fi

if [ -z "$PREFLIGHT_STATUS" ]; then PREFLIGHT_STATUS="PASS"; fi
echo "PREFLIGHT=$PREFLIGHT_STATUS"
if [ "$PREFLIGHT_STATUS" != "PASS" ]; then FN_STATUS="NOT_RUN"; PHASE_STATUS="FAIL"; fi

§2. Create fn_iu_save

if [ "$PREFLIGHT_STATUS" = "PASS" ]; then
echo "=== CREATE ==="
FN_EXIT=0
"${PSQL[@]}" -v fn_owner="$FN_OWNER" -v fn_grantees="$FN_GRANTEES" <<'FNSQL' || FN_EXIT=$?
BEGIN;
SELECT set_config('app.p3c3_grantees',:'fn_grantees',true);

CREATE FUNCTION public.fn_iu_save(
  p_address text,
  p_body text,
  p_actor text,
  p_title text DEFAULT NULL,
  p_reason text DEFAULT NULL,
  p_mode text DEFAULT 'auto'
) RETURNS jsonb LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path=pg_catalog,public AS $fn$
DECLARE
  v_iu record;
  v_policy text;
  v_dr jsonb;
  v_did uuid;
BEGIN
  IF btrim(COALESCE(p_address,''))='' THEN
    RETURN jsonb_build_object('status','invalid_input','field','address','guidance','Required.','next_action','fix_input');
  END IF;
  IF btrim(COALESCE(p_body,''))='' THEN
    RETURN jsonb_build_object('status','invalid_input','field','body','guidance','Required.','next_action','fix_input');
  END IF;
  IF btrim(COALESCE(p_actor,''))='' THEN
    RETURN jsonb_build_object('status','invalid_input','field','actor','guidance','Required.','next_action','fix_input');
  END IF;
  IF COALESCE(p_mode,'auto') NOT IN ('auto','draft') THEN
    RETURN jsonb_build_object('status','invalid_input','field','mode','guidance','Must be auto or draft.','valid_modes','auto, draft','next_action','fix_input');
  END IF;

  SELECT * INTO v_iu FROM public.information_unit WHERE canonical_address=btrim(p_address);

  IF NOT FOUND THEN
    RETURN public.fn_iu_create(
      btrim(p_address),
      COALESCE(NULLIF(btrim(COALESCE(p_title,'')), ''), btrim(p_address)),
      p_body,
      btrim(p_actor)
    );
  END IF;

  v_dr := public.fn_iu_create_edit_draft(btrim(p_address), p_body, btrim(p_actor), p_reason, p_title);
  IF v_dr->>'status' != 'draft_created' THEN
    RETURN v_dr;
  END IF;
  v_did := (v_dr->>'draft_id')::uuid;

  IF p_mode = 'draft' THEN
    RETURN jsonb_build_object('status','draft_created_review_required',
      'draft_id',v_did,'unit_id',v_iu.id,'mode','draft',
      'guidance','Draft created. Awaiting review.','next_action','fn_iu_apply_edit_draft');
  END IF;

  v_policy := COALESCE(
    v_iu.identity_profile->>'edit_policy',
    (SELECT value FROM public.dot_config WHERE key='iu_edit.policy.default_mode'),
    'auto_apply'
  );

  IF v_policy = 'auto_apply' THEN
    RETURN public.fn_iu_apply_edit_draft(v_did, btrim(p_actor),
      COALESCE(p_reason, 'auto-apply via fn_iu_save'));
  ELSE
    RETURN jsonb_build_object('status','draft_created_review_required',
      'draft_id',v_did,'unit_id',v_iu.id,'policy',v_policy,
      'guidance','Awaiting review.','next_action','fn_iu_apply_edit_draft');
  END IF;
END;
$fn$;

REVOKE ALL ON FUNCTION public.fn_iu_save(text,text,text,text,text,text) FROM PUBLIC;
DO $$DECLARE v_g text:=current_setting('app.p3c3_grantees');v_r text;
BEGIN FOREACH v_r IN ARRAY string_to_array(v_g,',') LOOP v_r:=btrim(v_r);
  IF v_r<>'' THEN EXECUTE format('GRANT EXECUTE ON FUNCTION public.fn_iu_save(text,text,text,text,text,text) TO %I',v_r); END IF;
END LOOP;END;$$;

COMMIT;
FNSQL
echo "FN_EXIT=$FN_EXIT"
[ "$FN_EXIT" = "0" ] && FN_STATUS="OK" || { FN_STATUS="FAIL"; PHASE_STATUS="FAIL"; }
fi

§3. Tests

if [ "$FN_STATUS" = "OK" ]; then
echo "=== TESTS ==="

# T1: New address → official IU
T1_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_save('$TEST_NEW_ADDR','P3C3 pilot body $TS','agent:p3c3') AS j)
SELECT j->>'status', j->>'unit_id', j->>'version_id', j::text FROM r;
EOSQL
)
T1_S=$(echo "$T1_RAW" | cut -d"$US" -f1)
T1_UID=$(echo "$T1_RAW" | cut -d"$US" -f2)
T1_VID=$(echo "$T1_RAW" | cut -d"$US" -f3)
T1_FULL=$(echo "$T1_RAW" | cut -d"$US" -f4)
echo "T1: s=$T1_S uid=$T1_UID vid=$T1_VID"
T1_IU=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit WHERE canonical_address='$TEST_NEW_ADDR';" | tr -d ' ')
T1_UV=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version WHERE unit_id=(SELECT id FROM information_unit WHERE canonical_address='$TEST_NEW_ADDR');" | tr -d ' ')
T1_SEQ=$("${PSQL[@]}" -t -A -c "SELECT version_seq FROM unit_version WHERE unit_id=(SELECT id FROM information_unit WHERE canonical_address='$TEST_NEW_ADDR');")
T1_INV=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('$TEST_NEW_ADDR'))->>'all_pass';")
[ "$T1_IU" = "1" ] && [ "$T1_UV" = "1" ] && [ "$T1_SEQ" = "1" ] && [ "$T1_INV" = "true" ] && echo "T1=PASS" || { echo "T1=FAIL iu=$T1_IU uv=$T1_UV seq=$T1_SEQ inv=$T1_INV full=$T1_FULL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T2: Existing, mode='draft' → draft only
UV_PRE_T2=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
T2_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_save('$TEST_NEW_ADDR','draft body $TS','agent:p3c3',NULL,NULL,'draft') AS j)
SELECT j->>'status', j->>'draft_id', j::text FROM r;
EOSQL
)
T2_S=$(echo "$T2_RAW" | cut -d"$US" -f1)
T2_DID=$(echo "$T2_RAW" | cut -d"$US" -f2)
UV_POST_T2=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
echo "T2: s=$T2_S did=$T2_DID uv=$UV_PRE_T2→$UV_POST_T2"
[ "$T2_S" = "draft_created_review_required" ] && [ "$UV_POST_T2" = "$UV_PRE_T2" ] && echo "T2=PASS" || { echo "T2=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T3: Existing, mode='auto' — policy-dependent
UV_PRE_T3=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
T3_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_save('$TEST_NEW_ADDR','auto body $TS','agent:p3c3') AS j)
SELECT j->>'status', j->>'draft_id', j->>'version_id', j::text FROM r;
EOSQL
)
T3_S=$(echo "$T3_RAW" | cut -d"$US" -f1)
T3_DID=$(echo "$T3_RAW" | cut -d"$US" -f2)
T3_VID=$(echo "$T3_RAW" | cut -d"$US" -f3)
UV_POST_T3=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
echo "T3: s=$T3_S policy=$POLICY_BEFORE uv=$UV_PRE_T3→$UV_POST_T3"
if [ "$POLICY_BEFORE" = "auto_apply" ]; then
  [ "$T3_S" = "applied" ] && [ "$UV_POST_T3" = "$((UV_PRE_T3+1))" ] && echo "T3=PASS" || { echo "T3=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
else
  [ "$T3_S" = "draft_created_review_required" ] && [ "$UV_POST_T3" = "$UV_PRE_T3" ] && echo "T3=PASS" || { echo "T3=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
fi

# T4: no_change (branch by T3)
if [ "$T3_S" = "applied" ]; then
  T4=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_save('$TEST_NEW_ADDR','auto body $TS','agent:p3c3'))->>'status';
EOSQL
)
  [ "$T4" = "no_change" ] && echo "T4=PASS" || { echo "T4=FAIL=$T4"; TEST_FAIL=$((TEST_FAIL+1)); }
else
  echo "T4=SKIPPED_POLICY_REQUIRE_REVIEW"
fi

# T5: Invalid mode
T5=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_save('x','y','z',NULL,NULL,'badmode'))->>'status';")
[ "$T5" = "invalid_input" ] && echo "T5=PASS" || { echo "T5=FAIL=$T5"; TEST_FAIL=$((TEST_FAIL+1)); }

# T6: Invalid inputs
T6A=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_save('','y','z'))->>'status';")
T6B=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_save('x','','z'))->>'status';")
T6C=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_save('x','y',''))->>'status';")
[ "$T6A" = "invalid_input" ] && [ "$T6B" = "invalid_input" ] && [ "$T6C" = "invalid_input" ] && echo "T6=PASS" || { echo "T6=FAIL a=$T6A b=$T6B c=$T6C"; TEST_FAIL=$((TEST_FAIL+1)); }

# T7: fn_iu_comment free-flow with draft context
COMMENT_PRE_T7=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
T7=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_comment('$TEST_NEW_ADDR','agent:p3c3','P3C3 test comment','note',NULL,jsonb_build_object('draft_id','$T2_DID')))->>'status';
EOSQL
)
COMMENT_POST_T7=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
echo "T7: s=$T7 comments=$COMMENT_PRE_T7→$COMMENT_POST_T7"
[ "$COMMENT_POST_T7" = "$((COMMENT_PRE_T7+1))" ] && echo "T7=PASS" || { echo "T7=FAIL=$T7"; TEST_FAIL=$((TEST_FAIL+1)); }

# T8: Source check
T8_PROBE=$("${PSQL[@]}" -t -A -F "$US" <<'SQL'
SELECT
  prosrc ~* 'insert[[:space:]]+into[[:space:]]+(public\.)?(information_unit|unit_version)' AS has_insert,
  prosrc ~* 'update[[:space:]]+(public\.)?(information_unit|unit_version)' AS has_update,
  prosrc ~* 'delete[[:space:]]+from[[:space:]]+(public\.)?(information_unit|unit_version)' AS has_delete,
  prosrc ~* 'canonical_writer' AS has_marker
FROM pg_proc WHERE proname='fn_iu_save' AND pronamespace='public'::regnamespace;
SQL
)
T8_INS=$(echo "$T8_PROBE" | cut -d"$US" -f1)
T8_UPD=$(echo "$T8_PROBE" | cut -d"$US" -f2)
T8_DEL=$(echo "$T8_PROBE" | cut -d"$US" -f3)
T8_MRK=$(echo "$T8_PROBE" | cut -d"$US" -f4)
[ "$T8_INS" = "f" ] && [ "$T8_UPD" = "f" ] && [ "$T8_DEL" = "f" ] && [ "$T8_MRK" = "f" ] && echo "T8=PASS" || { echo "T8=FAIL ins=$T8_INS upd=$T8_UPD del=$T8_DEL mrk=$T8_MRK"; TEST_FAIL=$((TEST_FAIL+1)); }

# T9: Security (rev3-F4: LATERAL for PUBLIC check)
T9_META=$("${PSQL[@]}" -t -A -F "$US" -c "SELECT prosecdef,proconfig,proowner::regrole FROM pg_proc WHERE proname='fn_iu_save' AND pronamespace='public'::regnamespace;")
T9_SD=$(echo "$T9_META" | cut -d"$US" -f1)
T9_PC=$(echo "$T9_META" | cut -d"$US" -f2)
T9_OWN=$(echo "$T9_META" | cut -d"$US" -f3)
T9="PASS"
[ "$T9_SD" = "t" ] || { T9="FAIL_SD"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "$T9_PC" | grep -q "pg_catalog" || { T9="FAIL_SP"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$T9_OWN" = "$FN_OWNER" ] || { T9="FAIL_OWN"; TEST_FAIL=$((TEST_FAIL+1)); }
PUB=$("${PSQL[@]}" -t -c "
  SELECT count(*) FROM pg_proc p
  LEFT JOIN LATERAL aclexplode(p.proacl) x ON true
  WHERE p.proname='fn_iu_save' AND p.pronamespace='public'::regnamespace
    AND x.grantee=0 AND x.privilege_type='EXECUTE';" | tr -d ' ')
[ "$PUB" = "0" ] || { T9="FAIL_PUB"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T9=$T9 sd=$T9_SD own=$T9_OWN"

# T10: Grantees
IFS=',' read -ra GR <<< "$FN_GRANTEES"
T10="PASS"
for R in "${GR[@]}"; do
  R=$(echo "$R" | tr -d ' '); [ -z "$R" ] && continue
  CAN=$("${PSQL_NOSTOP[@]}" -v role="$R" -v sig="public.fn_iu_save(text,text,text,text,text,text)" -t -A <<'CANSQL'
SELECT has_function_privilege(:'role',:'sig','EXECUTE');
CANSQL
)
  [ "$CAN" = "t" ] || { T10="FAIL_$R"; TEST_FAIL=$((TEST_FAIL+1)); }
done
echo "T10=$T10"

# T11: Gateway block
T11_OUT=$("${PSQL_NOSTOP[@]}" -c "INSERT INTO information_unit(id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile) VALUES(gen_random_uuid(),'test/p3c3/dw-$TS','t','draft','t','{}');" 2>&1) || true
echo "$T11_OUT" | grep -q "IU Gateway blocked" && echo "T11=PASS" || { echo "T11=FAIL output=$T11_OUT"; TEST_FAIL=$((TEST_FAIL+1)); }

# T12: Hashes unchanged + count=9 (rev3-F3)
P3C_HASHES_AFTER=$("${PSQL[@]}" -t -A -c "
  SELECT string_agg(p.proname||'='||md5(p.prosrc),'|' ORDER BY p.proname)
  FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid
  WHERE n.nspname='public' AND p.proname IN
    ('fn_iu_edit_plan','fn_iu_create_edit_draft','fn_iu_comment_edit_draft','fn_iu_comment',
     'fn_iu_apply_edit_draft','fn_iu_edit','fn_iu_create','fn_content_hash','fn_iu_verify_invariants');")
PROTECTED_COUNT_AFTER=$("${PSQL[@]}" -t -c "
  SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON p.pronamespace=n.oid
  WHERE n.nspname='public' AND p.proname IN
    ('fn_iu_edit_plan','fn_iu_create_edit_draft','fn_iu_comment_edit_draft','fn_iu_comment',
     'fn_iu_apply_edit_draft','fn_iu_edit','fn_iu_create','fn_content_hash','fn_iu_verify_invariants');" | tr -d ' ')
T12="PASS"
[ "$P3C_HASHES_AFTER" = "$P3C_HASHES_BEFORE" ] || { T12="FAIL_HASH"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$PROTECTED_COUNT_AFTER" = "9" ] || { T12="FAIL_COUNT=$PROTECTED_COUNT_AFTER"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T12=$T12 count_before=$PROTECTED_COUNT_BEFORE count_after=$PROTECTED_COUNT_AFTER"

# T13: Counts — IU/UV/Draft/Comment (rev3-F1: all 4 deltas)
IU_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
UV_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
DRAFT_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_draft;" | tr -d ' ')
COMMENT_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
echo "T13: IU=$IU_BEFORE→$IU_NOW UV=$UV_BEFORE→$UV_NOW D=$DRAFT_BEFORE→$DRAFT_NOW C=$COMMENT_BEFORE→$COMMENT_NOW"
T13="PASS"
# IU always +1 (T1)
[ "$IU_NOW" = "$((IU_BEFORE+1))" ] || { T13="FAIL_IU exp=$((IU_BEFORE+1)) got=$IU_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
if [ "$POLICY_BEFORE" = "auto_apply" ]; then
  # UV: T1+1, T3+1 = +2
  [ "$UV_NOW" = "$((UV_BEFORE+2))" ] || { T13="FAIL_UV exp=$((UV_BEFORE+2)) got=$UV_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
  # Draft: T2+1(open), T3+1(applied) = +2
  [ "$DRAFT_NOW" = "$((DRAFT_BEFORE+2))" ] || { T13="FAIL_D exp=$((DRAFT_BEFORE+2)) got=$DRAFT_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
  # Comment: T3+1(system), T7+1(user) = +2
  [ "$COMMENT_NOW" = "$((COMMENT_BEFORE+2))" ] || { T13="FAIL_C exp=$((COMMENT_BEFORE+2)) got=$COMMENT_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
else
  # UV: T1+1 only = +1
  [ "$UV_NOW" = "$((UV_BEFORE+1))" ] || { T13="FAIL_UV exp=$((UV_BEFORE+1)) got=$UV_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
  # Draft: T2+1(open), T3+1(open) = +2
  [ "$DRAFT_NOW" = "$((DRAFT_BEFORE+2))" ] || { T13="FAIL_D exp=$((DRAFT_BEFORE+2)) got=$DRAFT_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
  # Comment: T7+1(user) only = +1
  [ "$COMMENT_NOW" = "$((COMMENT_BEFORE+1))" ] || { T13="FAIL_C exp=$((COMMENT_BEFORE+1)) got=$COMMENT_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }
fi
echo "T13=$T13"

# T14: Policy unchanged
POLICY_AFTER=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_edit.policy.default_mode';")
[ "$POLICY_AFTER" = "$POLICY_BEFORE" ] && echo "T14=PASS" || { echo "T14=FAIL policy=$POLICY_BEFORE→$POLICY_AFTER"; TEST_FAIL=$((TEST_FAIL+1)); }

echo "TEST_FAIL=$TEST_FAIL"
fi

§4. Cleanup + Report

if [ "$TEST_FAIL" != "0" ] && [ "$FN_STATUS" = "OK" ]; then
  echo "=== CLEANUP ==="; DROP_FAIL=0
  "${PSQL_NOSTOP[@]}" -c "DROP FUNCTION IF EXISTS public.fn_iu_save(text,text,text,text,text,text);" 2>&1 || DROP_FAIL=1
  [ "$DROP_FAIL" = "0" ] && echo "cleanup=FUNCTION_DROPPED" || { echo "cleanup=CRITICAL"; PHASE_STATUS="CRITICAL"; }
fi

echo "=== FINAL ==="
if [ "$PHASE_STATUS" = "CRITICAL" ]; then true
elif [ "$PREFLIGHT_STATUS" != "PASS" ]; then PHASE_STATUS="FAIL"
elif [ "$FN_STATUS" != "OK" ]; then PHASE_STATUS="FAIL"
elif [ "$TEST_FAIL" = "0" ]; then PHASE_STATUS="PASS"
else PHASE_STATUS="${PHASE_STATUS:-FAIL}"; fi

echo "phase_status=$PHASE_STATUS"
echo "IU=$IU_BEFORE→$IU_NOW UV=$UV_BEFORE→$UV_NOW D=$DRAFT_BEFORE→$DRAFT_NOW C=$COMMENT_BEFORE→$COMMENT_NOW"
echo "policy=$POLICY_BEFORE (unchanged T14)"
echo "owner=$FN_OWNER"
echo "function_security=SECDEF_search_path_pg_catalog_public_PUBLIC_revoked"
echo "p3c_unchanged=$([ "$P3C_HASHES_AFTER" = "$P3C_HASHES_BEFORE" ] && echo true || echo false)"
echo "protected_function_count_before=$PROTECTED_COUNT_BEFORE"
echo "protected_function_count_after=$PROTECTED_COUNT_AFTER"

# rev3-F5: Pilot IDs
echo "test_new_addr=$TEST_NEW_ADDR"
echo "t1_status=$T1_S t1_unit_id=$T1_UID t1_version_id=$T1_VID"
echo "t2_draft_id=$T2_DID"
echo "t3_status=$T3_S t3_draft_id=$T3_DID t3_version_id=$T3_VID"
echo "t7_comment_status=$T7"

# rev3-F6: Cleanup semantics unconditional
echo "cleanup_on_test_fail=drop_fn_iu_save_only"
echo "test_rows_retained_on_fail=true"
echo "test_rows_retained_on_pass=true"

echo "=== AI INTERFACE (COMPLETE — 2 front-doors + 1 approval button) ==="
echo "fn_iu_save(address, body, actor)          -- create/edit content (AI front-door)"
echo "fn_iu_comment(address, actor, comment)    -- free-flow comment (AI front-door)"
echo "fn_iu_apply_edit_draft(draft_id, actor)   -- reviewer approval button"

echo "next_required_step=SWITCH_DEFAULT_POLICY_TO_REQUIRE_REVIEW"
echo "deferred=P3D_NOTIFICATION_OUTBOX_BEFORE_HERMES_PRODUCTION"
echo "LOG=$LOG"

echo "=== AGENT: UPLOAD REPORT ==="
echo "Path: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c3-iu-natural-save-router-report.md"
echo "Upload even on FAIL/CRITICAL."

P3C3 rev3 | all 4 count deltas, 9 protected functions verified, pilot IDs in report | 14 tests + T4 conditional | CHƯA dispatch | Chờ GPT/User review