KB-2781 rev 3

24-P3B-FU — Generalize fn_iu_verify_invariants — Execution Prompt (rev3)

17 min read Revision 3

24-P3B-FU — Generalize fn_iu_verify_invariants for post-edit IUs — Execution Prompt (rev3)

Date: 2026-05-07 Status: PROMPT rev3 — GPT 5 fixes: broad pre-COMMIT assertions, exact config check, DDL backup, T2 debug, report fields. Chờ final review. Report: knowledge/dev/laws/dieu44-trien-khai/reports/24-p3bfu-generalize-iu-invariant-report.md Predecessor: 23-p3c2-iu-apply-edit-functions-prompt.md rev7+ (FAIL — invariant birth-only) Successor: re-run 23-p3c2 after this passes.


Purpose

Replace public.fn_iu_verify_invariants(text) body so that i3_anchors_exact validates the IU's current anchors — not version_seq = 1. Birth checks (i4/i5) still inspect seq=1.

Signature, return shape, owner, security, search_path: unchanged.


Hard Boundaries

  • ❌ No table DDL / trigger / gateway changes
  • ❌ No alteration of any function other than fn_iu_verify_invariants(text)
  • ❌ No direct IU/UV/draft/comment writes
  • ❌ No vector mutation, no notification, no cleanup
  • ❌ Do not change signature, return type, owner, SECDEF, or search_path
  • ❌ Do not introduce a new overload
  • ✅ Single CREATE OR REPLACE FUNCTION inside one transaction with broad assertions before COMMIT

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/24-p3bfu.${TS}.log"
exec > >(tee -a "$LOG") 2>&1
PREFLIGHT_STATUS=""; FN_STATUS="NOT_RUN"; TEST_FAIL=0; PHASE_STATUS=""
INV_BEFORE_HASH=""; INV_AFTER_HASH=""
P3C1_HASHES_BEFORE=""; P3C1_HASHES_AFTER=""
INV_BEFORE_OWNER=""; INV_BEFORE_SECDEF=""; INV_BEFORE_CONFIG=""; INV_BEFORE_RETTYPE=""
INV_BEFORE_DDL=""
PILOT_ADDR=""; T1_JSON=""; T1_ALL_PASS=""
IU_BEFORE=""; UV_BEFORE=""
US=$'\x1f'
echo "=== P3B-FU START $TS ==="

§1. Preflight

echo "=== PREFLIGHT ==="

EX=$("${PSQL[@]}" -t -A -c "SELECT to_regprocedure('public.fn_iu_verify_invariants(text)');")
[ -n "$EX" ] && [ "$EX" != "-" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: fn missing"; }

if [ -z "$PREFLIGHT_STATUS" ]; then
  META=$("${PSQL[@]}" -t -A -F "$US" -c "
    SELECT md5(prosrc), proowner::regrole::text, prosecdef, proconfig::text, pg_get_function_result(oid)
    FROM pg_proc WHERE oid=to_regprocedure('public.fn_iu_verify_invariants(text)');")
  INV_BEFORE_HASH=$(echo "$META" | cut -d"$US" -f1)
  INV_BEFORE_OWNER=$(echo "$META" | cut -d"$US" -f2)
  INV_BEFORE_SECDEF=$(echo "$META" | cut -d"$US" -f3)
  INV_BEFORE_CONFIG=$(echo "$META" | cut -d"$US" -f4)
  INV_BEFORE_RETTYPE=$(echo "$META" | cut -d"$US" -f5)
  echo "BEFORE: hash=$INV_BEFORE_HASH owner=$INV_BEFORE_OWNER secdef=$INV_BEFORE_SECDEF config=$INV_BEFORE_CONFIG ret=$INV_BEFORE_RETTYPE"
  [ "$INV_BEFORE_RETTYPE" = "jsonb" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: ret!=jsonb"; }
fi

# rev3-F3: Capture full pre-function DDL for deterministic restore
if [ -z "$PREFLIGHT_STATUS" ]; then
  INV_BEFORE_DDL=$("${PSQL[@]}" -t -A -c "SELECT pg_get_functiondef(to_regprocedure('public.fn_iu_verify_invariants(text)'));")
  echo "--- PRE-FUNCTION DDL ---"
  echo "$INV_BEFORE_DDL"
  echo "--- END PRE-FUNCTION DDL ---"
  [ -n "$INV_BEFORE_DDL" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: could not capture DDL"; }
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  P3C1_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_create','fn_content_hash');")
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  PILOT_ADDR=$("${PSQL[@]}" -t -A -c "
    SELECT iu.canonical_address
    FROM information_unit iu
    JOIN unit_version uv ON uv.id=iu.version_anchor_ref
    WHERE uv.version_seq=1
    ORDER BY iu.canonical_address LIMIT 1;")
  [ -n "$PILOT_ADDR" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: no birth-anchored pilot"; }
  case "$PILOT_ADDR" in *"'"*) PREFLIGHT_STATUS="FAIL"; echo "FAIL: pilot addr quote" ;; esac
  echo "PILOT=$PILOT_ADDR"
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  PRE_PASS=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('$PILOT_ADDR'))->>'all_pass';")
  [ "$PRE_PASS" = "true" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: pilot pre all_pass=$PRE_PASS"; }
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  IU_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
  UV_BEFORE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
  echo "IU=$IU_BEFORE UV=$UV_BEFORE"
fi

if [ -z "$PREFLIGHT_STATUS" ]; then PREFLIGHT_STATUS="PASS"; fi
echo "PREFLIGHT=$PREFLIGHT_STATUS"

§2. Replace function (atomic; broad assertions before COMMIT)

Shell strategy: quoted heredoc <<'FNSQL' preserves $fn$ and $$. Pilot address via psql -v + set_config.

Safety: DO assertion block runs pilot, all-IU, error paths, and source markers ALL inside the transaction. Any failure → RAISE EXCEPTION → rollback → old function intact.

if [ "$PREFLIGHT_STATUS" = "PASS" ]; then
echo "=== REPLACE ==="
FN_EXIT=0
"${PSQL[@]}" -v pilot_addr="$PILOT_ADDR" <<'FNSQL' || FN_EXIT=$?
BEGIN;

SELECT set_config('app.p3bfu_pilot', :'pilot_addr', true);

CREATE OR REPLACE FUNCTION public.fn_iu_verify_invariants(p_addr text)
RETURNS jsonb LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path=pg_catalog,public AS $fn$
DECLARE
  v_iu_id uuid; v_var uuid; v_car text;
  v_uv_current record; v_uv_birth record;
  v_birth_count int; v_uv_strategy text;
  v_has_birth boolean; v_anchor_exact boolean; v_uv_birth_ok boolean;
BEGIN
  IF p_addr IS NULL OR btrim(p_addr)='' THEN
    RETURN jsonb_build_object('status','invalid_input','message','canonical_address NULL/empty');
  END IF;
  SELECT id, version_anchor_ref, content_anchor_ref
    INTO v_iu_id, v_var, v_car
  FROM public.information_unit WHERE canonical_address = btrim(p_addr);
  IF v_iu_id IS NULL THEN
    RETURN jsonb_build_object('status','not_found','canonical_address',btrim(p_addr));
  END IF;
  SELECT * INTO v_uv_current FROM public.unit_version WHERE id = v_var;
  SELECT count(*) INTO v_birth_count FROM public.unit_version WHERE unit_id = v_iu_id AND version_seq = 1;
  IF v_birth_count = 0 THEN
    RETURN jsonb_build_object('i1_iu_exists',true,'i2_uv_linked',(v_uv_current.id IS NOT NULL),
      'i3_anchors_exact',false,'i4_birth_exists',false,'i5_uv_birth_ok',true,
      'all_pass',false,'issue','missing_birth_version');
  END IF;
  IF v_birth_count > 1 THEN
    RETURN jsonb_build_object('i1_iu_exists',true,'i2_uv_linked',(v_uv_current.id IS NOT NULL),
      'i3_anchors_exact',false,'i4_birth_exists',false,'i5_uv_birth_ok',true,
      'all_pass',false,'issue','duplicate_birth_version');
  END IF;
  SELECT * INTO v_uv_birth FROM public.unit_version WHERE unit_id = v_iu_id AND version_seq = 1;
  v_anchor_exact := COALESCE(v_uv_current.id IS NOT NULL AND v_var = v_uv_current.id
    AND v_car = v_uv_current.id::text AND v_uv_current.unit_id = v_iu_id, false);
  v_has_birth := COALESCE((SELECT EXISTS(SELECT 1 FROM public.birth_registry
    WHERE collection_name='information_unit' AND entity_code='information_unit::'||v_iu_id::text)), false);
  SELECT birth_code_strategy INTO v_uv_strategy FROM public.collection_registry WHERE collection_name='unit_version';
  IF v_uv_strategy='subordinate' OR v_uv_strategy IS NULL THEN
    v_uv_birth_ok := NOT EXISTS(SELECT 1 FROM public.birth_registry
      WHERE collection_name='unit_version' AND entity_code='unit_version::'||v_uv_birth.id::text);
  ELSE
    RETURN jsonb_build_object('i1_iu_exists',true,'i2_uv_linked',(v_uv_current.id IS NOT NULL),
      'i3_anchors_exact',v_anchor_exact,'i4_birth_exists',v_has_birth,'i5_uv_birth_ok',false,
      'all_pass',false,'issue','uv_strategy_changed','uv_strategy',v_uv_strategy);
  END IF;
  RETURN jsonb_build_object('i1_iu_exists',true,'i2_uv_linked',(v_uv_current.id IS NOT NULL),
    'i3_anchors_exact',v_anchor_exact,'i4_birth_exists',v_has_birth,'i5_uv_birth_ok',v_uv_birth_ok,
    'all_pass',((v_uv_current.id IS NOT NULL) AND v_anchor_exact AND v_has_birth AND v_uv_birth_ok));
END;
$fn$;

-- rev3-F1: Broad assertions — ALL inside transaction, BEFORE COMMIT
-- If any fails → RAISE EXCEPTION → rollback → old function intact
DO $$
DECLARE
  v_j jsonb;
  v_pilot text := current_setting('app.p3bfu_pilot');
  v_bad int;
  v_null_s text; v_empty_s text; v_bogus_s text;
  v_has_var boolean; v_has_seq1 boolean; v_has_br boolean;
BEGIN
  -- A1: pilot all_pass
  v_j := public.fn_iu_verify_invariants(v_pilot);
  IF NOT COALESCE((v_j->>'all_pass')::boolean, false) THEN
    RAISE EXCEPTION 'A1_pilot_failed: %', v_j;
  END IF;

  -- A2: all existing IUs all_pass
  SELECT count(*) INTO v_bad FROM public.information_unit iu
  WHERE NOT COALESCE(((public.fn_iu_verify_invariants(iu.canonical_address))->>'all_pass')::boolean, false);
  IF v_bad > 0 THEN
    RAISE EXCEPTION 'A2_all_iu_failed: % IUs not all_pass', v_bad;
  END IF;

  -- A3: error paths
  v_null_s := (public.fn_iu_verify_invariants(NULL))->>'status';
  v_empty_s := (public.fn_iu_verify_invariants(''))->>'status';
  v_bogus_s := (public.fn_iu_verify_invariants('zzz/no/such/iu'))->>'status';
  IF v_null_s != 'invalid_input' OR v_empty_s != 'invalid_input' OR v_bogus_s != 'not_found' THEN
    RAISE EXCEPTION 'A3_error_path_failed: null=% empty=% bogus=%', v_null_s, v_empty_s, v_bogus_s;
  END IF;

  -- A4: source markers
  SELECT prosrc ~* 'version_anchor_ref',
         prosrc ~* 'version_seq[[:space:]]*=[[:space:]]*1',
         prosrc ~* 'birth_registry'
  INTO v_has_var, v_has_seq1, v_has_br
  FROM pg_proc WHERE oid = to_regprocedure('public.fn_iu_verify_invariants(text)');
  IF NOT (v_has_var AND v_has_seq1 AND v_has_br) THEN
    RAISE EXCEPTION 'A4_source_marker_failed: var=% seq1=% br=%', v_has_var, v_has_seq1, v_has_br;
  END IF;
END$$;

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

§3. Tests (post-commit evidence — safety already enforced by pre-COMMIT assertions)

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

# T1: pilot all_pass (post-commit verification)
T1_JSON=$("${PSQL[@]}" -t -A -c "SELECT public.fn_iu_verify_invariants('$PILOT_ADDR');")
T1_ALL_PASS=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('$PILOT_ADDR'))->>'all_pass';")
[ "$T1_ALL_PASS" = "true" ] && echo "T1=PASS" || { echo "T1=FAIL=$T1_ALL_PASS json=$T1_JSON"; TEST_FAIL=$((TEST_FAIL+1)); }

# T2: ALL existing IUs all_pass (rev3-F4: print failing addresses on fail)
T2_BAD=$("${PSQL[@]}" -t -A -c "
  SELECT count(*) FROM information_unit iu
  WHERE NOT COALESCE(((public.fn_iu_verify_invariants(iu.canonical_address))->>'all_pass')::boolean, false);")
if [ "$T2_BAD" = "0" ]; then echo "T2=PASS"
else
  echo "T2=FAIL bad=$T2_BAD"
  TEST_FAIL=$((TEST_FAIL+1))
  echo "--- T2 FAILING IUs (up to 10) ---"
  "${PSQL[@]}" -t -A -c "
    SELECT iu.canonical_address, public.fn_iu_verify_invariants(iu.canonical_address)
    FROM information_unit iu
    WHERE NOT COALESCE(((public.fn_iu_verify_invariants(iu.canonical_address))->>'all_pass')::boolean, false)
    LIMIT 10;"
  echo "--- END T2 ---"
fi

# T3: invalid_input on null/empty
T3A=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants(NULL))->>'status';")
T3B=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants(''))->>'status';")
T3C=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('   '))->>'status';")
[ "$T3A" = "invalid_input" ] && [ "$T3B" = "invalid_input" ] && [ "$T3C" = "invalid_input" ] && echo "T3=PASS" || { echo "T3=FAIL a=$T3A b=$T3B c=$T3C"; TEST_FAIL=$((TEST_FAIL+1)); }

# T4: not_found on bogus address
T4=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('zzz/no/such/iu'))->>'status';")
[ "$T4" = "not_found" ] && echo "T4=PASS" || { echo "T4=FAIL=$T4"; TEST_FAIL=$((TEST_FAIL+1)); }

# T5: signature unchanged ((text)->jsonb)
T5_SIG=$("${PSQL[@]}" -t -A -c "SELECT pg_get_function_arguments(oid)||' -> '||pg_get_function_result(oid) FROM pg_proc WHERE oid=to_regprocedure('public.fn_iu_verify_invariants(text)');")
[ "$T5_SIG" = "p_addr text -> jsonb" ] && echo "T5=PASS" || { echo "T5=FAIL=$T5_SIG"; TEST_FAIL=$((TEST_FAIL+1)); }

# T6: owner / SECDEF / search_path EXACT match (rev3-F2)
META_AFTER=$("${PSQL[@]}" -t -A -F "$US" -c "SELECT proowner::regrole::text, prosecdef, proconfig::text FROM pg_proc WHERE oid=to_regprocedure('public.fn_iu_verify_invariants(text)');")
OWN_A=$(echo "$META_AFTER" | cut -d"$US" -f1)
SD_A=$(echo "$META_AFTER" | cut -d"$US" -f2)
PC_A=$(echo "$META_AFTER" | cut -d"$US" -f3)
T6="PASS"
[ "$OWN_A" = "$INV_BEFORE_OWNER" ] || { T6="FAIL_OWN own=$OWN_A exp=$INV_BEFORE_OWNER"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$SD_A" = "$INV_BEFORE_SECDEF" ] || { T6="FAIL_SD sd=$SD_A exp=$INV_BEFORE_SECDEF"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$PC_A" = "$INV_BEFORE_CONFIG" ] || { T6="FAIL_CFG cfg=$PC_A exp=$INV_BEFORE_CONFIG"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T6=$T6 own=$OWN_A secdef=$SD_A cfg=$PC_A"

# T7: hash actually changed
INV_AFTER_HASH=$("${PSQL[@]}" -t -A -c "SELECT md5(prosrc) FROM pg_proc WHERE oid=to_regprocedure('public.fn_iu_verify_invariants(text)');")
[ "$INV_AFTER_HASH" != "$INV_BEFORE_HASH" ] && echo "T7=PASS" || { echo "T7=FAIL hash unchanged"; TEST_FAIL=$((TEST_FAIL+1)); }

# T8: P3C1 + create + content_hash hashes UNCHANGED
P3C1_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_create','fn_content_hash');")
[ "$P3C1_HASHES_AFTER" = "$P3C1_HASHES_BEFORE" ] && echo "T8=PASS" || { echo "T8=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T9: source markers
T9_PROBE=$("${PSQL[@]}" -t -A -F "$US" <<'SQL'
SELECT prosrc ~* 'version_anchor_ref',
       prosrc ~* 'version_seq[[:space:]]*=[[:space:]]*1',
       prosrc ~* 'birth_registry'
FROM pg_proc WHERE oid=to_regprocedure('public.fn_iu_verify_invariants(text)');
SQL
)
T9A=$(echo "$T9_PROBE" | cut -d"$US" -f1)
T9B=$(echo "$T9_PROBE" | cut -d"$US" -f2)
T9C=$(echo "$T9_PROBE" | cut -d"$US" -f3)
[ "$T9A" = "t" ] && [ "$T9B" = "t" ] && [ "$T9C" = "t" ] && echo "T9=PASS" || { echo "T9=FAIL var=$T9A seq1=$T9B br=$T9C"; TEST_FAIL=$((TEST_FAIL+1)); }

# T10: state counts unchanged
IU_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit;" | tr -d ' ')
UV_NOW=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$IU_NOW" = "$IU_BEFORE" ] && [ "$UV_NOW" = "$UV_BEFORE" ] && echo "T10=PASS" || { echo "T10=FAIL IU=$IU_NOW UV=$UV_NOW"; TEST_FAIL=$((TEST_FAIL+1)); }

echo "TEST_FAIL=$TEST_FAIL"
fi

§4. Final

echo "=== FINAL ==="
if [ "$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="FAIL"; fi

echo "phase_status=$PHASE_STATUS"
echo "preflight=$PREFLIGHT_STATUS fn=$FN_STATUS test_fail=$TEST_FAIL"
echo "before_hash=$INV_BEFORE_HASH after_hash=$INV_AFTER_HASH"
echo "owner=$INV_BEFORE_OWNER (T6 exact match verified)"
echo "secdef=$INV_BEFORE_SECDEF config=$INV_BEFORE_CONFIG (T6 exact match verified)"
echo "p3c1_unchanged=$([ "$P3C1_HASHES_AFTER" = "$P3C1_HASHES_BEFORE" ] && echo true || echo false)"
echo "pilot=$PILOT_ADDR pilot_all_pass=$T1_ALL_PASS"
echo "pre_function_ddl_backup=included_in_log"
echo "IU=$IU_BEFORE→$IU_NOW UV=$UV_BEFORE→$UV_NOW"
# rev3-F5: explicit next pack and P3C2 path
echo "next_required_pack=23-P3C2_RE_RUN_AFTER_INVARIANT_GENERALIZED"
echo "p3c2_prompt=knowledge/dev/laws/dieu44-trien-khai/prompts/23-p3c2-iu-apply-edit-functions-prompt.md"
echo "LOG=$LOG"

echo "=== AGENT: UPLOAD REPORT ==="
echo "Path: knowledge/dev/laws/dieu44-trien-khai/reports/24-p3bfu-generalize-iu-invariant-report.md"
echo "Include: verdict, A1-A4 assertion results, T1-T10, before/after hash,"
echo "  owner+secdef+config exact match verified, signature unchanged,"
echo "  P3C1 hashes unchanged, pilot all_pass, all-IU sanity,"
echo "  state counts, source markers, pre-function DDL backup reference."
echo "Upload even on FAIL."

P3B-FU rev3 | broad pre-COMMIT assertions (A1-A4) + DDL backup + exact config check + T2 debug | unblocks 23-P3C2