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_writermarker 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