23-P3C4 — Policy Switch + Agent Context Pack — Execution Prompt (rev5)
23-P3C4 — Policy Switch + Agent Context Pack — Execution Prompt (rev5)
Date: 2026-05-07 Status: PROMPT rev5 — GPT 5 fixes: rollback after upload gate, two-part execution, separated upload status, policy_final. Chờ final review. CHƯA dispatch. Report: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c4-iu-policy-and-agent-context-report.md
Hard Boundaries
- ❌ No function changes (T8 verifies 10 hashes)
- ❌ No table DDL / trigger / gateway
- ❌ No vector / notification implementation / cleanup
- ❌ Only 1 dot_config key may change
- ❌ No raw rollback in context pack
Execution Order (rev5 — rollback AFTER upload gate)
1. Preflight
2. Switch policy (rowcount-guarded)
3. DB tests (T1-T8)
4. Write context + anchor content to temp files
5. Agent Data: upload + verify context pack + P3D anchor
6. Upload gate: set CONTEXT_UPLOAD
7. Rollback decision (has full picture: tests + upload)
8. Query policy_final
9. Final report
PART A — DB Shell
#!/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-p3c4.${TS}.log"
exec > >(tee -a "$LOG") 2>&1
PREFLIGHT_STATUS=""; SWITCH_STATUS=""; TEST_FAIL=0; PHASE_STATUS=""
POLICY_BEFORE=""; POLICY_AFTER=""; POLICY_ACTION=""; POLICY_FINAL=""
ROLLBACK_STATUS="NOT_RUN"
P3C_HASHES_BEFORE=""; P3C_HASHES_AFTER=""
PROTECTED_COUNT_BEFORE=""; PROTECTED_COUNT_AFTER=""
TEST_NEW_ADDR=""
T3_DID=""; T3_S=""; T3_GUID=""; T3_NA=""; T3_FULL=""
T4_S=""; T4_VID=""; T5_DID=""; T6_S=""
IU_BEFORE=""; UV_BEFORE=""; DRAFT_BEFORE=""; COMMENT_BEFORE=""
IU_NOW=""; UV_NOW=""; DRAFT_NOW=""; COMMENT_NOW=""
CONTEXT_UPLOAD="NOT_RUN"
CTX_PACK_UPLOAD="NOT_RUN"; P3D_ANCHOR_UPLOAD="NOT_RUN"
US=$'\x1f'
echo "=== P3C4 START $TS ==="
# === PREFLIGHT ===
echo "=== PREFLIGHT ==="
KEY_COUNT=$("${PSQL[@]}" -t -c "SELECT count(*) FROM dot_config WHERE key='iu_edit.policy.default_mode';" | tr -d ' ')
[ "$KEY_COUNT" = "1" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: key count=$KEY_COUNT"; }
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_BEFORE=$POLICY_BEFORE"
if [ "$POLICY_BEFORE" = "require_review" ]; then POLICY_ACTION="SKIPPED_ALREADY_REQUIRE_REVIEW"
elif [ "$POLICY_BEFORE" = "auto_apply" ]; then POLICY_ACTION="UPDATED_AUTO_APPLY_TO_REQUIRE_REVIEW"
else PREFLIGHT_STATUS="FAIL"; echo "FAIL: unexpected policy=$POLICY_BEFORE"; fi
fi
if [ -z "$PREFLIGHT_STATUS" ]; then
for SIG in "public.fn_iu_save(text,text,text,text,text,text)" \
"public.fn_iu_apply_edit_draft(uuid,text,text)" \
"public.fn_iu_comment(text,text,text,text,text,jsonb)"; do
R=$("${PSQL[@]}" -t -A -c "SELECT to_regprocedure('$SIG');")
[ -n "$R" ] && [ "$R" != "-" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: $SIG"; break; }
done
fi
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','fn_iu_save');")
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','fn_iu_save');" | tr -d ' ')
[ "$PROTECTED_COUNT_BEFORE" = "10" ] || { PREFLIGHT_STATUS="FAIL"; echo "FAIL: count=$PROTECTED_COUNT_BEFORE"; }
fi
if [ -z "$PREFLIGHT_STATUS" ]; then
TEST_NEW_ADDR="test/p3c4/pilot-${TS}"
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 "TEST=$TEST_NEW_ADDR 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"
# === SWITCH ===
if [ "$PREFLIGHT_STATUS" = "PASS" ] && [ "$POLICY_ACTION" = "UPDATED_AUTO_APPLY_TO_REQUIRE_REVIEW" ]; then
echo "=== SWITCH ==="
SWITCH_ROWS=$("${PSQL[@]}" -t -A <<'SQL'
WITH u AS (
UPDATE dot_config SET value='require_review'
WHERE key='iu_edit.policy.default_mode' AND value='auto_apply'
RETURNING 1
) SELECT count(*) FROM u;
SQL
)
[ "$SWITCH_ROWS" = "1" ] || { PHASE_STATUS="FAIL"; echo "FAIL: rows=$SWITCH_ROWS"; }
POLICY_AFTER=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_edit.policy.default_mode';")
[ "$POLICY_AFTER" = "require_review" ] || { PHASE_STATUS="FAIL"; echo "FAIL: verify=$POLICY_AFTER"; }
SWITCH_STATUS="APPLIED"
elif [ "$POLICY_ACTION" = "SKIPPED_ALREADY_REQUIRE_REVIEW" ]; then
POLICY_AFTER="require_review"; SWITCH_STATUS="SKIPPED"
fi
# === TESTS ===
if [ "$PREFLIGHT_STATUS" = "PASS" ] && [ -z "$PHASE_STATUS" ]; then
echo "=== TESTS ==="
T1=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_edit.policy.default_mode';")
[ "$T1" = "require_review" ] && echo "T1=PASS" || { echo "T1=FAIL=$T1"; TEST_FAIL=$((TEST_FAIL+1)); }
T2_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_save('$TEST_NEW_ADDR','P3C4 pilot $TS','agent:p3c4') AS j)
SELECT j->>'status', j->>'unit_id', j->>'version_id', j::text FROM r;
EOSQL
)
T2_S=$(echo "$T2_RAW" | cut -d"$US" -f1); T2_UID=$(echo "$T2_RAW" | cut -d"$US" -f2); T2_FULL=$(echo "$T2_RAW" | cut -d"$US" -f4)
T2_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 ' ')
T2_SMIN=$("${PSQL[@]}" -t -A -c "SELECT min(version_seq) FROM unit_version WHERE unit_id=(SELECT id FROM information_unit WHERE canonical_address='$TEST_NEW_ADDR');")
T2_SMAX=$("${PSQL[@]}" -t -A -c "SELECT max(version_seq) FROM unit_version WHERE unit_id=(SELECT id FROM information_unit WHERE canonical_address='$TEST_NEW_ADDR');")
T2_INV=$("${PSQL[@]}" -t -A -c "SELECT (public.fn_iu_verify_invariants('$TEST_NEW_ADDR'))->>'all_pass';")
[ "$T2_UV" = "1" ] && [ "$T2_SMIN" = "1" ] && [ "$T2_SMAX" = "1" ] && [ "$T2_INV" = "true" ] && echo "T2=PASS" || { echo "T2=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
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','rr body $TS','agent:p3c4') AS j)
SELECT j->>'status', j->>'draft_id', j->>'policy', j->>'guidance', j->>'next_action', j::text FROM r;
EOSQL
)
T3_S=$(echo "$T3_RAW" | cut -d"$US" -f1); T3_DID=$(echo "$T3_RAW" | cut -d"$US" -f2); T3_POL=$(echo "$T3_RAW" | cut -d"$US" -f3)
T3_GUID=$(echo "$T3_RAW" | cut -d"$US" -f4); T3_NA=$(echo "$T3_RAW" | cut -d"$US" -f5); T3_FULL=$(echo "$T3_RAW" | cut -d"$US" -f6)
UV_POST_T3=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
echo "T3: s=$T3_S did=$T3_DID pol=$T3_POL guid=$T3_GUID na=$T3_NA"
echo "T3_SAMPLE=$T3_FULL"
T3_OK="true"
[ "$T3_S" = "draft_created_review_required" ] || T3_OK="false"
[ "$UV_POST_T3" = "$UV_PRE_T3" ] || T3_OK="false"
[ -n "$T3_DID" ] || T3_OK="false"
{ [ -n "$T3_GUID" ] || [ -n "$T3_NA" ]; } || T3_OK="false"
[ "$T3_OK" = "true" ] && echo "T3=PASS" || { echo "T3=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
if [ -n "$T3_DID" ]; then
UV_PRE_T4=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
T4_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_apply_edit_draft('$T3_DID','reviewer:gpt','P3C4 approve') AS j)
SELECT j->>'status', j->>'version_id', j->>'version_seq' FROM r;
EOSQL
)
T4_S=$(echo "$T4_RAW" | cut -d"$US" -f1); T4_VID=$(echo "$T4_RAW" | cut -d"$US" -f2); T4_SEQ=$(echo "$T4_RAW" | cut -d"$US" -f3)
UV_POST_T4=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$T4_S" = "applied" ] && [ "$UV_POST_T4" = "$((UV_PRE_T4+1))" ] && echo "T4=PASS" || { echo "T4=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
else echo "T4=FAIL (no T3_DID)"; TEST_FAIL=$((TEST_FAIL+1)); fi
UV_PRE_T5=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
T5_RAW=$("${PSQL[@]}" -t -A -F "$US" <<EOSQL
WITH r AS (SELECT public.fn_iu_save('$TEST_NEW_ADDR','draft body $TS','agent:p3c4',NULL,NULL,'draft') AS j)
SELECT j->>'status', j->>'draft_id' FROM r;
EOSQL
)
T5_S=$(echo "$T5_RAW" | cut -d"$US" -f1); T5_DID=$(echo "$T5_RAW" | cut -d"$US" -f2)
UV_POST_T5=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_version;" | tr -d ' ')
[ "$T5_S" = "draft_created_review_required" ] && [ "$UV_POST_T5" = "$UV_PRE_T5" ] && echo "T5=PASS" || { echo "T5=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
COMMENT_PRE=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
T6_S=$("${PSQL[@]}" -t -A <<EOSQL
SELECT (public.fn_iu_comment('$TEST_NEW_ADDR','agent:p3c4','P3C4 comment','general','agent',jsonb_build_object('draft_id','$T5_DID')))->>'status';
EOSQL
)
COMMENT_POST=$("${PSQL[@]}" -t -c "SELECT count(*) FROM unit_edit_comment;" | tr -d ' ')
[ "$T6_S" = "comment_added" ] && [ "$COMMENT_POST" = "$((COMMENT_PRE+1))" ] && echo "T6=PASS" || { echo "T6=FAIL s=$T6_S"; TEST_FAIL=$((TEST_FAIL+1)); }
T7_OUT=$("${PSQL_NOSTOP[@]}" -c "INSERT INTO information_unit(id,canonical_address,unit_kind,lifecycle_status,owner_ref,identity_profile) VALUES(gen_random_uuid(),'test/p3c4/dw-$TS','t','draft','t','{}');" 2>&1) || true
echo "$T7_OUT" | grep -q "IU Gateway blocked" && echo "T7=PASS" || { echo "T7=FAIL"; TEST_FAIL=$((TEST_FAIL+1)); }
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','fn_iu_save');")
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','fn_iu_save');" | tr -d ' ')
T8="PASS"
[ "$P3C_HASHES_AFTER" = "$P3C_HASHES_BEFORE" ] || { T8="FAIL_HASH"; TEST_FAIL=$((TEST_FAIL+1)); }
[ "$PROTECTED_COUNT_AFTER" = "10" ] || { T8="FAIL_COUNT=$PROTECTED_COUNT_AFTER"; TEST_FAIL=$((TEST_FAIL+1)); }
echo "T8=$T8"
echo "TEST_FAIL=$TEST_FAIL"
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 ' ')
fi
echo "=== END PART A (DB) ==="
echo "TEST_FAIL=$TEST_FAIL PHASE_STATUS=$PHASE_STATUS POLICY_ACTION=$POLICY_ACTION"
PART B — Agent Data Upload + Verify
Agent: after Part A completes, execute these Agent Data operations.
If Part A TEST_FAIL=0 and PHASE_STATUS is empty (not FAIL):
- Upload context pack:
- Path:
knowledge/dev/laws/dieu44-trien-khai/context-packs/iu-agent-front-door-context.md - Title:
IU Agent Front-Door Context - Tags:
iu, agent, context, fn_iu_save, fn_iu_comment - Content:
- Path:
# IU Agent Front-Door Context
> AI/Agent: hướng dẫn hệ thống Information Unit.
> Không cần nhớ quy trình. 2 hàm chính + 1 nút duyệt.
## Tạo hoặc sửa nội dung
```sql
SELECT fn_iu_save('địa-chỉ', 'nội dung', 'actor');
- Địa chỉ mới → phiên bản 1 chính thức.
- Địa chỉ đã có → nháp chờ duyệt.
- Nội dung giống → no_change.
fn_iu_save(addr, body, actor, title, reason, 'draft')→ luôn nháp.
Góp ý
SELECT fn_iu_comment('địa-chỉ', 'actor', 'comment', 'general', 'agent', '{}'::jsonb);
Tự do, không phê duyệt.
Phê duyệt nháp (reviewer)
SELECT fn_iu_apply_edit_draft('draft-id', 'reviewer', 'ghi chú');
Quy tắc
- KHÔNG ghi trực tiếp vào information_unit / unit_version.
- Nháp chưa apply ≠ bản chính.
- Mọi status trả về có guidance + next_action.
- Actor nên ổn định: gpt, opus, agent:codex, reviewer:gpt.
- Rollback policy phải qua reviewed pack. Không raw UPDATE.
Thông báo (chưa bật)
Notification inbox chưa có (chờ P3D). P3D sẽ tạo danh sách riêng cho comment mới, draft mới, apply mới. Mỗi reviewer/actor có trạng thái đã xem riêng — GPT xem rồi không mất trạng thái của Opus. Trước P3D, dùng report/draft/comment trực tiếp khi được giao.
2. **Verify context pack:** Read back document, confirm content exists. Set `CTX_PACK_UPLOAD=PASS` or `FAIL`.
3. **Upload P3D anchor:**
- Path: `knowledge/dev/laws/dieu44-trien-khai/design/23-p3d-notification-outbox-read-state-design-anchor.md`
- Title: `P3D — Notification Outbox + Read State — Design Anchor`
- Tags: `p3d, design, notification, outbox, read-state`
- Content:
```markdown
# P3D — Notification Outbox + Read State — Design Anchor
> Date: 2026-05-07
> Status: DESIGN ANCHOR — chưa implementation.
## Nguyên tắc
- PG-first / PG-native / PG-driven
- Lightweight event lists, KHÔNG general activity log
- PG triggers on: comment insert, draft create, apply/update
- Tách 2 loại event: comment events + official update/apply events
## Per-actor read state
- Mỗi reviewer/actor có trạng thái đã xem riêng
- GPT mark seen → chỉ ẩn với GPT
- Opus chưa seen → vẫn hiện với Opus
- Query unread items của chính mình
- Không global read flag
## Implementation
- Deferred to P3D
- Cần: notification table(s), per-actor read_state table, PG triggers
- Tận dụng PG LISTEN/NOTIFY nếu phù hợp
-
Verify P3D anchor: Read back. Set
P3D_ANCHOR_UPLOAD=PASSorFAIL. -
Set combined:
CONTEXT_UPLOAD=PASSonly if BOTH = PASS. ElseCONTEXT_UPLOAD=FAIL.
If Part A had TEST_FAIL>0 or PHASE_STATUS=FAIL, skip uploads, set all to NOT_RUN.
PART C — Rollback Decision + Final (after upload gate)
echo "=== PART C ==="
# Agent sets these from Part B:
# CTX_PACK_UPLOAD=PASS|FAIL|NOT_RUN
# P3D_ANCHOR_UPLOAD=PASS|FAIL|NOT_RUN
# CONTEXT_UPLOAD=PASS|FAIL|NOT_RUN
# Upload gate: tests pass but upload fail → PHASE_STATUS=FAIL
if [ "$TEST_FAIL" = "0" ] && [ -z "$PHASE_STATUS" ] && [ "$CONTEXT_UPLOAD" != "PASS" ]; then
PHASE_STATUS="FAIL"
[ "$CONTEXT_UPLOAD" = "NOT_RUN" ] || CONTEXT_UPLOAD="FAIL_NOT_VERIFIED"
fi
# === ROLLBACK (rev5-F1: AFTER upload gate, has full picture) ===
if [ "$POLICY_ACTION" = "UPDATED_AUTO_APPLY_TO_REQUIRE_REVIEW" ] && \
{ [ "$TEST_FAIL" != "0" ] || [ "$PHASE_STATUS" = "FAIL" ]; }; then
echo "=== ROLLBACK ==="
ROLLBACK_ROWS=$("${PSQL_NOSTOP[@]}" -t -A <<'SQL'
WITH u AS (
UPDATE dot_config SET value='auto_apply'
WHERE key='iu_edit.policy.default_mode' AND value='require_review'
RETURNING 1
) SELECT count(*) FROM u;
SQL
)
if [ "$ROLLBACK_ROWS" = "1" ]; then
ROLLBACK_STATUS="PASS_POLICY_RESTORED_AUTO_APPLY"
else
ROLLBACK_STATUS="CRITICAL_ROLLBACK_FAILED"; PHASE_STATUS="CRITICAL"
fi
elif [ "$POLICY_ACTION" = "SKIPPED_ALREADY_REQUIRE_REVIEW" ] && \
{ [ "$TEST_FAIL" != "0" ] || [ "$PHASE_STATUS" = "FAIL" ]; }; then
ROLLBACK_STATUS="NOT_RUN_ALREADY_SWITCHED"
fi
echo "ROLLBACK_STATUS=$ROLLBACK_STATUS"
# Query actual final policy
POLICY_FINAL=$("${PSQL[@]}" -t -A -c "SELECT value FROM dot_config WHERE key='iu_edit.policy.default_mode';")
# === FINAL ===
echo "=== FINAL ==="
if [ "$PHASE_STATUS" = "CRITICAL" ]; then true
elif [ -z "$PHASE_STATUS" ] && [ "$TEST_FAIL" = "0" ] && [ "$CONTEXT_UPLOAD" = "PASS" ]; then PHASE_STATUS="PASS"
elif [ -z "$PHASE_STATUS" ]; then PHASE_STATUS="FAIL"; fi
echo "phase_status=$PHASE_STATUS"
echo "policy_action=$POLICY_ACTION"
echo "policy_before=$POLICY_BEFORE"
echo "policy_after_switch=$POLICY_AFTER"
echo "policy_final=$POLICY_FINAL"
echo "switch_status=$SWITCH_STATUS"
echo "rollback_status=$ROLLBACK_STATUS"
echo "context_pack_upload=$CTX_PACK_UPLOAD"
echo "p3d_anchor_upload=$P3D_ANCHOR_UPLOAD"
echo "context_upload=$CONTEXT_UPLOAD"
echo "IU=$IU_BEFORE→$IU_NOW UV=$UV_BEFORE→$UV_NOW D=$DRAFT_BEFORE→$DRAFT_NOW C=$COMMENT_BEFORE→$COMMENT_NOW"
echo "functions_unchanged=$([ "$P3C_HASHES_AFTER" = "$P3C_HASHES_BEFORE" ] && echo true || echo false)"
echo "protected_count=$PROTECTED_COUNT_BEFORE→$PROTECTED_COUNT_AFTER"
echo "test_new_addr=$TEST_NEW_ADDR"
echo "t3_status=$T3_S t3_draft_id=$T3_DID t3_guidance=$T3_GUID t3_next_action=$T3_NA"
echo "t4_status=$T4_S t4_version_id=$T4_VID"
echo "t5_draft_id=$T5_DID"
echo "t6_comment_status=$T6_S"
echo "cleanup_on_test_fail=rollback_policy_only_if_this_pack_changed_policy"
echo "test_rows_retained=true"
echo "notification_design=PG_NATIVE_EVENT_OUTBOX_WITH_PER_ACTOR_READ_STATE"
echo "notification_implementation=DEFERRED_P3D"
echo "p3d_anchor=knowledge/dev/laws/dieu44-trien-khai/design/23-p3d-notification-outbox-read-state-design-anchor.md"
echo "=== AI INTERFACE (PRODUCTION — require_review) ==="
echo "fn_iu_save(address, body, actor) -- create/edit (new=official, existing=draft)"
echo "fn_iu_comment(address, actor, comment) -- free-flow comment"
echo "fn_iu_apply_edit_draft(draft_id, actor) -- reviewer approval"
echo "pack23_status=MINIMUM_EDIT_WORKFLOW_COMPLETE"
echo "LOG=$LOG"
echo "=== AGENT: UPLOAD REPORT ==="
echo "Path: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3c4-iu-policy-and-agent-context-report.md"
echo "Upload even on FAIL/CRITICAL."
P3C4 rev5 | rollback AFTER upload gate, two-part execution, policy_final, separated upload status | 8 tests | CHƯA dispatch | Chờ GPT/User review