KB-34B4 rev 6

23-P3C2 — Apply + Wrapper Functions — Execution Prompt (rev6)

28 min read Revision 6

23-P3C2 — Apply + Wrapper Functions — Execution Prompt (rev6)

Date: 2026-05-07 Status: PROMPT rev6 — chờ GPT/User final review. CHƯA dispatch. Report: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c2-iu-apply-edit-functions-report.md Rev5→Rev6: 2 fixes (DRAFT_B cùng unit_id, T16 POSIX regex class). Rev6 Opus note: GPT yêu cầu DRAFT_B cùng unit_id + base_version_ref. Opus chỉ áp dụng unit_id — function stale ALL open drafts cùng unit_id bất kể base_version_ref. Overconstraint base_version_ref có thể gây false FAIL.


Setup

#!/usr/bin/env bash
# All shell comparisons: [ "$A" = "$B" ] with spaces around =
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-p3c2.${TS}.log"
exec > >(tee -a "$LOG") 2>&1
PREFLIGHT_STATUS=""; FN_STATUS="NOT_RUN"; TEST_FAIL=0
PHASE_STATUS=""; P3D_READINESS="BLOCKED"
IU_BEFORE=""; UV_BEFORE=""; DRAFT_BEFORE=""; COMMENT_BEFORE=""
IU_NOW=""; UV_NOW=""; DRAFT_NOW=""; COMMENT_NOW=""
FN_OWNER=""; FN_GRANTEES=""; UV_LIFECYCLE=""
DRAFT_A_ID=""; DRAFT_B_ID=""; TEST_ADDR_A=""
P3C1_HASHES_BEFORE=""; DROP_FAIL="0"; CLEANUP_STATUS="NOT_RUN"
# rev5-P4: Initialize test output variables to avoid unset references on early exit
NEW_UV_ID=""
NEW_SEQ=""
T11_UV=""
T1_FULL=""
T1_INV=""
P3C1_HASHES_AFTER=""
# Unit separator for safe JSON field parsing
US=$'\x1f'
echo "=== P3C2 START $TS ==="

§1. Preflight

echo "=== PREFLIGHT ==="
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: gw"; }

if [ -z "$PREFLIGHT_STATUS" ]; then
  for TBL in unit_edit_draft unit_edit_comment information_unit unit_version; 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"; }
  done
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  P3C1_COUNT=$("${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');" | tr -d ' ')
  [ "$P3C1_COUNT" = "4" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: P3C1=$P3C1_COUNT"; }
  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');")
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  for FN in fn_iu_apply_edit_draft fn_iu_edit; do
    FC=$("${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';" | tr -d ' ')
    [ "$FC" = "0" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: $FN exists"; }
  done
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  for SIG in "public.fn_content_hash(text)" "public.fn_iu_verify_invariants(uuid)"; do
    R=$("${PSQL[@]}" -t -A -c "SELECT to_regprocedure('$SIG');")
    [ -n "$R" ] && [ "$R" != "-" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: $SIG"; }
  done
fi

# Unique exactly 1
if [ -z "$PREFLIGHT_STATUS" ]; then
  UQ=$("${PSQL[@]}" -t -c "
    SELECT count(*) FROM pg_index i WHERE i.indrelid='unit_version'::regclass AND i.indisunique=true
      AND (SELECT array_agg(a.attname ORDER BY ord) FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum,ord)
           JOIN pg_attribute a ON a.attrelid=i.indrelid AND a.attnum=k.attnum)=ARRAY['unit_id','version_seq'];
  " | tr -d ' ')
  [ "$UQ" = "1" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: unique(unit_id,version_seq) count=$UQ"; }
fi

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_create';")
  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_create' 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

if [ -z "$PREFLIGHT_STATUS" ]; then
  UV_LC_COUNT=$("${PSQL[@]}" -t -c "SELECT count(DISTINCT lifecycle_status) FROM unit_version;" | tr -d ' ')
  UV_LIFECYCLE=$("${PSQL[@]}" -t -A -c "SELECT DISTINCT lifecycle_status FROM unit_version;")
  echo "UV_LC=$UV_LIFECYCLE count=$UV_LC_COUNT"
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  AV=$("${PSQL[@]}" -t -c "SELECT count(*) FROM information_unit WHERE version_anchor_ref IS NOT NULL AND content_anchor_ref!=version_anchor_ref::text;" | tr -d ' ')
  [ "$AV" = "0" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: anchor=$AV"; }
fi

# rev6-F1: DRAFT_B must share unit_id with DRAFT_A (stale logic is per unit_id)
if [ -z "$PREFLIGHT_STATUS" ]; then
  DRAFT_A_ID=$("${PSQL[@]}" -t -A -c "
    SELECT id FROM unit_edit_draft
    WHERE draft_status='open'
    ORDER BY created_at LIMIT 1;")
  DRAFT_B_ID=$("${PSQL[@]}" -t -A -c "
    WITH a AS (
      SELECT unit_id FROM unit_edit_draft WHERE id='$DRAFT_A_ID'
    )
    SELECT d.id FROM unit_edit_draft d, a
    WHERE d.draft_status='open'
      AND d.id != '$DRAFT_A_ID'
      AND d.unit_id = a.unit_id
    ORDER BY d.created_at LIMIT 1;")
  TEST_ADDR_A=$("${PSQL[@]}" -t -A -c "SELECT canonical_address FROM unit_edit_draft WHERE id='$DRAFT_A_ID';")
  echo "A=$DRAFT_A_ID B=$DRAFT_B_ID ADDR=$TEST_ADDR_A"
  # rev5-P3: Require both DRAFT_A and DRAFT_B for stale-base evidence
  [ -n "$DRAFT_A_ID" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: no DRAFT_A"; }
  [ -n "$DRAFT_B_ID" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: no DRAFT_B for stale-base test (must share unit_id with A)"; }
fi

# rev5-P5: Validate interpolated test values before any raw shell interpolation into SQL
if [ -z "$PREFLIGHT_STATUS" ]; then
  UUID_RE='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
  [[ "$DRAFT_A_ID" =~ $UUID_RE ]] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: DRAFT_A_ID invalid UUID"; }
  [[ "$DRAFT_B_ID" =~ $UUID_RE ]] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: DRAFT_B_ID invalid UUID"; }
  [ -n "$TEST_ADDR_A" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: TEST_ADDR_A empty"; }
  case "$TEST_ADDR_A" in
    *"'"*) PREFLIGHT_STATUS="FAIL"; echo "FAIL: TEST_ADDR_A contains single quote" ;;
  esac
fi

if [ -z "$PREFLIGHT_STATUS" ]; then
  DW=$("${PSQL_NOSTOP[@]}" -c "INSERT INTO information_unit(id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile) VALUES(gen_random_uuid(),'test/p3c2/dw','t','draft','t','{}');" 2>&1) || true
  if echo "$DW" | grep -q "IU Gateway blocked"; then echo "GATE_DW=PASS"
  else PREFLIGHT_STATUS="FAIL"; echo "FAIL: dw not gateway-blocked: $DW"; fi
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 ' ')
  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"; P3D_READINESS="BLOCKED"; fi

§2. Create Functions (SQL bodies identical to rev4 — lifecycle self-determine, single IU UPDATE, content_anchor_ref=uuid::text)

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.p3c2_grantees',:'fn_grantees',true);

CREATE FUNCTION public.fn_iu_apply_edit_draft(
  p_draft_id uuid, p_actor text, p_review_note text DEFAULT NULL
) RETURNS jsonb LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path=pg_catalog,public AS $fn$
DECLARE
  v_draft record;v_iu record;v_uv record;v_hash text;v_next_seq int;v_new_uv_id uuid;
  v_uv_lifecycle text;v_lc_count int;v_stale_count int;v_inv jsonb;v_comment_body text;v_comment_id uuid;
BEGIN
  IF p_draft_id IS NULL THEN RETURN jsonb_build_object('status','invalid_input','field','draft_id','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;
  SELECT * INTO v_draft FROM public.unit_edit_draft WHERE id=p_draft_id;
  IF NOT FOUND THEN RETURN jsonb_build_object('status','draft_not_found','draft_id',p_draft_id,'guidance','Check draft_id.','next_action','verify_draft_id');END IF;
  IF v_draft.draft_status!='open' THEN RETURN jsonb_build_object('status','draft_not_open','draft_id',p_draft_id,'current_status',v_draft.draft_status,'guidance','Only open.','next_action','fn_iu_create_edit_draft');END IF;
  SELECT * INTO v_iu FROM public.information_unit WHERE id=v_draft.unit_id FOR UPDATE;
  IF v_iu.version_anchor_ref!=v_draft.base_version_ref THEN
    UPDATE public.unit_edit_draft SET draft_status='stale_base',stale_at=now() WHERE id=p_draft_id;
    RETURN jsonb_build_object('status','stale_base','draft_id',p_draft_id,'guidance','Base changed.','next_action','fn_iu_create_edit_draft');
  END IF;
  SELECT * INTO v_uv FROM public.unit_version WHERE id=v_iu.version_anchor_ref;
  v_hash:=public.fn_content_hash(v_draft.draft_body);
  IF v_hash!=v_draft.draft_content_hash THEN RETURN jsonb_build_object('status','draft_hash_mismatch','draft_id',p_draft_id,'guidance','Corrupted.','next_action','fn_iu_create_edit_draft');END IF;
  IF v_hash=v_uv.content_hash AND v_draft.draft_title IS NULL THEN RETURN jsonb_build_object('status','no_change','guidance','Identical.','next_action','none');END IF;
  SELECT count(DISTINCT lifecycle_status),min(lifecycle_status) INTO v_lc_count,v_uv_lifecycle FROM public.unit_version;
  IF v_lc_count!=1 OR v_uv_lifecycle IS NULL OR btrim(v_uv_lifecycle)='' THEN
    RETURN jsonb_build_object('status','lifecycle_ambiguous','guidance','Not uniquely determined.','next_action','stop_for_gpt_review');
  END IF;
  SELECT COALESCE(MAX(version_seq),0)+1 INTO v_next_seq FROM public.unit_version WHERE unit_id=v_iu.id;
  PERFORM set_config('app.canonical_writer','fn_iu_apply_edit_draft',true);
  INSERT INTO public.unit_version(id,unit_id,body,content_hash,version_seq,lifecycle_status,created_by,created_at)
  VALUES(gen_random_uuid(),v_iu.id,v_draft.draft_body,v_hash,v_next_seq,v_uv_lifecycle,btrim(p_actor),now())
  RETURNING id INTO v_new_uv_id;
  UPDATE public.information_unit SET version_anchor_ref=v_new_uv_id,content_anchor_ref=v_new_uv_id::text,
    updated_at=now(),updated_by=btrim(p_actor),
    identity_profile=CASE WHEN v_draft.draft_title IS NOT NULL THEN jsonb_set(identity_profile,'{title}',to_jsonb(v_draft.draft_title)) ELSE identity_profile END
  WHERE id=v_iu.id;
  UPDATE public.unit_edit_draft SET draft_status='applied',applied_at=now(),applied_by=btrim(p_actor),applied_version_ref=v_new_uv_id WHERE id=p_draft_id;
  UPDATE public.unit_edit_draft SET draft_status='stale_base',stale_at=now() WHERE unit_id=v_iu.id AND id!=p_draft_id AND draft_status='open';
  GET DIAGNOSTICS v_stale_count=ROW_COUNT;
  v_comment_body:=COALESCE(p_review_note,format('Applied as v%s by %s',v_next_seq,btrim(p_actor)));
  INSERT INTO public.unit_edit_comment(draft_id,unit_id,author_ref,author_type,comment_body,comment_kind)
  VALUES(p_draft_id,v_iu.id,btrim(p_actor),'system',v_comment_body,'system') RETURNING id INTO v_comment_id;
  v_inv:=public.fn_iu_verify_invariants(v_iu.id);
  IF NOT (v_inv->>'all_pass')::boolean THEN RAISE EXCEPTION 'invariant_failed: %',v_inv;END IF;
  RETURN jsonb_build_object('status','applied','draft_id',p_draft_id,'version_id',v_new_uv_id,
    'version_seq',v_next_seq,'unit_id',v_iu.id,'lifecycle_status',v_uv_lifecycle,
    'stale_drafts_count',v_stale_count,'comment_id',v_comment_id,'invariants',v_inv,
    'guidance','Published.','next_action','none');
END;$fn$;

CREATE FUNCTION public.fn_iu_edit(
  p_address text,p_body text,p_actor text,p_reason text DEFAULT NULL,p_title text DEFAULT NULL,p_review_note text DEFAULT NULL
) RETURNS jsonb LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path=pg_catalog,public AS $fn$
DECLARE v_policy text;v_iu record;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;
  SELECT * INTO v_iu FROM public.information_unit WHERE canonical_address=btrim(p_address);
  IF NOT FOUND THEN RETURN jsonb_build_object('status','iu_not_found','address',btrim(p_address),'guidance','Check address.','next_action','verify_address');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');
  v_dr:=public.fn_iu_create_edit_draft(p_address,p_body,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 v_policy='auto_apply' THEN RETURN public.fn_iu_apply_edit_draft(v_did,p_actor,COALESCE(p_review_note,'auto-apply'));
  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_apply_edit_draft(uuid,text,text) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.fn_iu_edit(text,text,text,text,text,text) FROM PUBLIC;
DO $$DECLARE v_g text:=current_setting('app.p3c2_grantees');v_r text;
  v_sigs text[]:=ARRAY['public.fn_iu_apply_edit_draft(uuid,text,text)','public.fn_iu_edit(text,text,text,text,text,text)'];v_s text;
BEGIN FOREACH v_r IN ARRAY string_to_array(v_g,',') LOOP v_r:=btrim(v_r);
  IF v_r<>'' THEN FOREACH v_s IN ARRAY v_sigs LOOP EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO %I',v_s,v_r);END LOOP;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 (T1 single call with unit separator)

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

# T1: Apply DRAFT_A — SINGLE CALL, unit separator for safe parse
T1_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_apply_edit_draft('$DRAFT_A_ID','agent:p3c2','P3C2 test') AS j)
SELECT j->>'status', j->>'version_id', j->>'version_seq', j->>'stale_drafts_count',
       j->'invariants'->>'all_pass', j::text
FROM r;
EOSQL
)
T1_S=$(echo "$T1_RAW" | cut -d"$US" -f1)
NEW_UV_ID=$(echo "$T1_RAW" | cut -d"$US" -f2)
NEW_SEQ=$(echo "$T1_RAW" | cut -d"$US" -f3)
T1_STALE=$(echo "$T1_RAW" | cut -d"$US" -f4)
T1_INV=$(echo "$T1_RAW" | cut -d"$US" -f5)
T1_FULL=$(echo "$T1_RAW" | cut -d"$US" -f6)
echo "T1: s=$T1_S vid=$NEW_UV_ID seq=$NEW_SEQ stale=$T1_STALE inv=$T1_INV"
[ "$T1_S" = "applied" ] && echo "T1=PASS" || { echo "T1=FAIL=$T1_S"; TEST_FAIL=$((TEST_FAIL+1)); }

# T2: UV +1
UV_T1=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$UV_T1" = "$((UV_BEFORE+1))" ] && echo "T2=PASS" || { echo "T2=FAIL uv=$UV_T1"; TEST_FAIL=$((TEST_FAIL+1)); }

# T3: Anchors
T3_P=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
SELECT version_anchor_ref, content_anchor_ref FROM information_unit
WHERE id=(SELECT unit_id FROM unit_edit_draft WHERE id='$DRAFT_A_ID');
EOSQL
)
T3_H=$(echo "$T3_P" | cut -d"$US" -f1); T3_C=$(echo "$T3_P" | cut -d"$US" -f2)
[ "$T3_H" = "$NEW_UV_ID" ] && [ "$T3_C" = "$NEW_UV_ID" ] && echo "T3=PASS" || { echo "T3=FAIL h=$T3_H c=$T3_C exp=$NEW_UV_ID"; TEST_FAIL=$((TEST_FAIL+1)); }

# T4: Draft A applied
T4_P=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
SELECT draft_status, applied_version_ref, applied_at IS NOT NULL, applied_by FROM unit_edit_draft WHERE id='$DRAFT_A_ID';
EOSQL
)
T4_ST=$(echo "$T4_P" | cut -d"$US" -f1); T4_VR=$(echo "$T4_P" | cut -d"$US" -f2); T4_AT=$(echo "$T4_P" | cut -d"$US" -f3)
[ "$T4_ST" = "applied" ] && [ "$T4_VR" = "$NEW_UV_ID" ] && [ "$T4_AT" = "t" ] && echo "T4=PASS" || { echo "T4=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T5: System comment
T5=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment WHERE draft_id='$DRAFT_A_ID' AND comment_kind='system';" | tr -d ' ')
[ "$T5" -ge "1" ] && echo "T5=PASS" || { echo "T5=FAIL=$T5"; TEST_FAIL=$((TEST_FAIL+1)); }

# T6: Draft B stale (rev5: DRAFT_B guaranteed by preflight, rev6: same unit_id)
T6_P=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
SELECT draft_status, stale_at IS NOT NULL FROM unit_edit_draft WHERE id='$DRAFT_B_ID';
EOSQL
)
T6_ST=$(echo "$T6_P" | cut -d"$US" -f1); T6_SA=$(echo "$T6_P" | cut -d"$US" -f2)
[ "$T6_ST" = "stale_base" ] && [ "$T6_SA" = "t" ] && echo "T6=PASS" || { echo "T6=FAIL st=$T6_ST sa=$T6_SA"; TEST_FAIL=$((TEST_FAIL+1)); }

# T7: Already applied → draft_not_open
T7=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_apply_edit_draft('$DRAFT_A_ID','x'))->>'status';
EOSQL
)
[ "$T7" = "draft_not_open" ] && echo "T7=PASS" || { echo "T7=FAIL=$T7"; TEST_FAIL=$((TEST_FAIL+1)); }

# T8: No UV from T7
UV_T7=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$UV_T7" = "$UV_T1" ] && echo "T8=PASS" || { echo "T8=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T9: Stale B → draft_not_open (rev5: DRAFT_B guaranteed, rev6: same unit_id)
T9=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_apply_edit_draft('$DRAFT_B_ID','x'))->>'status';
EOSQL
)
[ "$T9" = "draft_not_open" ] && echo "T9=PASS" || { echo "T9=FAIL=$T9"; TEST_FAIL=$((TEST_FAIL+1)); }

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

# T11: fn_iu_edit auto_apply (single call, US delimiter)
T11_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_edit('$TEST_ADDR_A','fn_iu_edit body $TS','agent:p3c2-edit','reason') AS j)
SELECT j->>'status', j->>'version_seq', j->>'version_id', j::text FROM r;
EOSQL
)
T11_S=$(echo "$T11_RAW" | cut -d"$US" -f1)
T11_UV=$(echo "$T11_RAW" | cut -d"$US" -f3)
T11_FULL=$(echo "$T11_RAW" | cut -d"$US" -f4)
echo "T11=$T11_S"
[ "$T11_S" = "applied" ] && echo "T11=PASS" || { echo "T11=FAIL=$T11_S"; TEST_FAIL=$((TEST_FAIL+1)); }

# T12: UV +1 more
UV_T11=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$UV_T11" = "$((UV_BEFORE+2))" ] && echo "T12=PASS" || { echo "T12=FAIL uv=$UV_T11"; TEST_FAIL=$((TEST_FAIL+1)); }

# T13: no_change
T13=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_edit('$TEST_ADDR_A','fn_iu_edit body $TS','agent:p3c2-edit'))->>'status';
EOSQL
)
[ "$T13" = "no_change" ] && echo "T13=PASS" || { echo "T13=FAIL=$T13"; TEST_FAIL=$((TEST_FAIL+1)); }

# T14: No UV from no_change
UV_T13=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$UV_T13" = "$UV_T11" ] && echo "T14=PASS" || { echo "T14=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

# T15: Security + owner
T15="PASS"
for SIG in "fn_iu_apply_edit_draft(uuid,text,text)" "fn_iu_edit(text,text,text,text,text,text)"; do
  META=$("${PSQL[@]}" -t -A -F "$US" -c "SELECT prosecdef,proconfig,proowner::regrole FROM pg_proc WHERE oid=to_regprocedure('public.$SIG');")
  SD=$(echo "$META" | cut -d"$US" -f1); PC=$(echo "$META" | cut -d"$US" -f2); OWN=$(echo "$META" | cut -d"$US" -f3)
  [ "$SD" = "t" ] || { T15="FAIL_DEF_$SIG"; TEST_FAIL=$((TEST_FAIL+1)); }
  echo "$PC" | grep -q "pg_catalog" || { T15="FAIL_SP_$SIG"; TEST_FAIL=$((TEST_FAIL+1)); }
  [ "$OWN" = "$FN_OWNER" ] || { T15="FAIL_OWN_$SIG own=$OWN exp=$FN_OWNER"; TEST_FAIL=$((TEST_FAIL+1)); }
done
PUB=$("${PSQL[@]}" -t -c "SELECT count(*) FROM (SELECT (aclexplode(proacl)).grantee g,(aclexplode(proacl)).privilege_type p FROM pg_proc WHERE proname IN('fn_iu_apply_edit_draft','fn_iu_edit') AND pronamespace='public'::regnamespace) x WHERE x.g=0 AND x.p='EXECUTE';" | tr -d ' ')
[ "$PUB" = "0" ] || { T15="FAIL_PUB"; TEST_FAIL=$((TEST_FAIL+1)); }
# Grantee via heredoc+psql vars
IFS=',' read -ra GR <<< "$FN_GRANTEES"
for R in "${GR[@]}"; do
  R=$(echo "$R" | tr -d ' '); [ -z "$R" ] && continue
  for SIG in "fn_iu_apply_edit_draft(uuid,text,text)" "fn_iu_edit(text,text,text,text,text,text)"; do
    CAN=$("${PSQL_NOSTOP[@]}" -v role="$R" -v sig="public.$SIG" -t -A <<'CANSQL'
SELECT has_function_privilege(:'role',:'sig','EXECUTE');
CANSQL
)
    [ "$CAN" = "t" ] || { T15="FAIL_GR_$R"; TEST_FAIL=$((TEST_FAIL+1)); }
  done
done
echo "T15=$T15"

# rev6-F2: T16 — Source verification with POSIX character classes (no backslash ambiguity)
# Apply: must have gateway marker, INSERT UV, UPDATE IU, NO DELETE IU/UV
T16_AP=$("${PSQL[@]}" -t -A -F "$US" <<'SQL'
SELECT
  prosrc ~* 'app\.canonical_writer' AS has_marker,
  prosrc ~* 'insert[[:space:]]+into[[:space:]]+(public\.)?unit_version' AS has_uv_insert,
  prosrc ~* 'update[[:space:]]+(public\.)?information_unit' AS has_iu_update,
  prosrc ~* 'delete[[:space:]]+from[[:space:]]+(public\.)?(information_unit|unit_version)' AS has_delete
FROM pg_proc
WHERE proname='fn_iu_apply_edit_draft'
  AND pronamespace='public'::regnamespace;
SQL
)
T16_AP_MARKER=$(echo "$T16_AP" | cut -d"$US" -f1)
T16_AP_UV_INSERT=$(echo "$T16_AP" | cut -d"$US" -f2)
T16_AP_IU_UPDATE=$(echo "$T16_AP" | cut -d"$US" -f3)
T16_AP_DELETE=$(echo "$T16_AP" | cut -d"$US" -f4)
if [ "$T16_AP_MARKER" = "t" ] && \
   [ "$T16_AP_UV_INSERT" = "t" ] && \
   [ "$T16_AP_IU_UPDATE" = "t" ] && \
   [ "$T16_AP_DELETE" = "f" ]; then
  echo "T16_APPLY=PASS"
else
  echo "T16_APPLY=FAIL marker=$T16_AP_MARKER uv_insert=$T16_AP_UV_INSERT iu_update=$T16_AP_IU_UPDATE delete=$T16_AP_DELETE"
  TEST_FAIL=$((TEST_FAIL+1))
fi

# Wrapper: must NOT contain marker, direct UV/IU writes, or deletes
T16_WR=$("${PSQL[@]}" -t -A -c "SELECT prosrc~*'app\.canonical_writer' OR prosrc~*'insert[[:space:]]+into[[:space:]]+(public\.)?unit_version' OR prosrc~*'update[[:space:]]+(public\.)?information_unit' OR prosrc~*'insert[[:space:]]+into[[:space:]]+(public\.)?information_unit' OR prosrc~*'update[[:space:]]+(public\.)?unit_version' OR prosrc~*'delete[[:space:]]+from' FROM pg_proc WHERE proname='fn_iu_edit' AND pronamespace='public'::regnamespace;" | tr -d ' ')
[ "$T16_WR" = "f" ] && echo "T16_WR=PASS" || { echo "T16_WR=FAIL=$T16_WR"; TEST_FAIL=$((TEST_FAIL+1)); }

# T17: P3C1 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');")
[ "$P3C1_HASHES_AFTER" = "$P3C1_HASHES_BEFORE" ] && echo "T17=PASS" || { echo "T17=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }

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

# T19: Invariant all_pass from T1 (from single successful call)
[ "$T1_INV" = "true" ] && echo "T19=PASS" || { echo "T19=FAIL inv=$T1_INV"; TEST_FAIL=$((TEST_FAIL+1)); }

echo "TEST_FAIL=$TEST_FAIL"
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 ' ')
fi

§4. Cleanup + Report

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

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

echo "phase_status=$PHASE_STATUS p3d_readiness=$P3D_READINESS"
echo "IU=$IU_BEFORE→$IU_NOW UV=$UV_BEFORE→$UV_NOW D=$DRAFT_BEFORE→$DRAFT_NOW C=$COMMENT_BEFORE→$COMMENT_NOW"
echo "uv_lifecycle=$UV_LIFECYCLE owner=$FN_OWNER"
echo "content_anchor_ref_verified=new_uv_id_text"
# rev5-P6: Security metadata in final report
echo "function_owner_verified=$FN_OWNER"
echo "function_security=SECDEF_search_path_pg_catalog_public_public_execute_absent_grantees_ok"
echo "invariant_all_pass=$T1_INV"
echo "invariant_json=$T1_FULL"
echo "pilot: A=$DRAFT_A_ID(applied) B=$DRAFT_B_ID(stale) UV1=$NEW_UV_ID UV2=$T11_UV ADDR=$TEST_ADDR_A"
echo "p3c1_unchanged=$([ "$P3C1_HASHES_AFTER" = "$P3C1_HASHES_BEFORE" ] && echo true || echo false)"
echo "official_test_rows_retained=true"
echo "notification_outbox=DEFERRED_P3D"
# rev5-P7: P3D roadmap exact fields preserved
echo "notification_roadmap=knowledge/dev/laws/dieu44-trien-khai/design/23-p3d-notification-outbox-roadmap-note.md"
echo "next_required_pack=P3D_NOTIFICATION_OUTBOX_BEFORE_HERMES_PRODUCTION"
echo "LOG=$LOG"

echo "=== AI INTERFACE (COMPLETE) ==="
echo "fn_iu_edit_plan(address,body,actor)         -- preview"
echo "fn_iu_create_edit_draft(address,body,actor)  -- draft"
echo "fn_iu_comment(address,author,body)           -- comment"
echo "fn_iu_apply_edit_draft(draft_id,actor)       -- apply"
echo "fn_iu_edit(address,body,actor,reason)        -- one-step"

echo "=== AGENT: UPLOAD REPORT ==="
echo "Path: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c2-iu-apply-edit-functions-report.md"
echo "Include: verdict, T1-T19, sigs, security+owner, lifecycle, invariant JSON from T1,"
echo "  anchor verified, draft transitions, counts, pilots, P3C1 hashes, P3D deferred."
echo "Upload even on FAIL/CRITICAL."

Hard Boundaries

  • ❌ No table DDL / trigger / gateway
  • ❌ No direct IU/UV writes outside fn_iu_apply_edit_draft
  • ❌ No alter P3C1 (T17)
  • ❌ No vector / cleanup / retry / notification

P3C2 rev6 | rev5 + 2 fixes (DRAFT_B same unit_id, POSIX regex) | CHƯA dispatch | Chờ GPT/User final review