KB-459B

Gate A — Production DDL Execution Prompt Design v0.5 FINAL

20 min read Revision 1
gate-aproduction-ddlp9dieu38prompt-designv0.5

Gate A — Production DDL Execution Prompt Design v0.5 FINAL

Status: DRAFT v0.5 FINAL — patched per GPT R5 conditional PASS Phiên: S186 | Ngày: 2026-04-28 Scope: Gate A ONLY — Production DDL (14 tables + 6 fn + 6 trg → public) KHÔNG bao gồm: Gate B (collections), Gate C (seed), G8B, G11, P9 Executor: Agent (Claude Code) via SSH contaboTẤT CẢ lệnh chạy trên VPS, KHÔNG phải local Mac Effort: medium-high Authorize chain: Opus v0.1 → GPT R1 → v0.2 → GPT R2 → v0.3 → GPT R3 → v0.4 → GPT R4 → v0.5 → GPT R5 → User GO Revision: v0.1 (S185) → v0.2 (R1, 5p) → v0.3 (R2, 6p) → v0.4 (R3, 6p) → v0.5 (R4, 3p + R5, 3p)


⚠️ PROMPT NÀY CHƯA ĐƯỢC AUTHORIZE

Chưa ai được chạy. Cần GPT PASS + User GO trước khi dispatch.

⚠️ MỌI LỆNH CHẠY TRÊN VPS (GPT R3#6)

Tất cả path /opt/incomex/data/tac/gate-a/ và lệnh docker exec chạy trên VPS qua ssh contabo. Agent KHÔNG chạy trên local Mac. Nếu agent phát hiện mình đang ở Mac → STOP, SSH vào VPS trước.


0. Mục tiêu

Tạo 14 tac_* tables + 6 functions + 6 triggers trong public schema trên production PostgreSQL. Bước đầu tiên đưa TAC schema từ dry-run (PASS G6 Run 4) sang production.

PASS criteria: Tất cả post-checks Q1–Q8 PASS. Directus healthy (hoặc DB-accessible nếu HTTP auth-protected). Production không bị ảnh hưởng ngoài tac_* objects mới.

FAIL criteria: psql exit ≠ 0, hoặc bất kỳ Q1–Q8 FAIL, hoặc Directus/DB unhealthy → rollback theo đúng path (§3.3 hoặc §5).

Evidence base: G6 Run 4 action log (reports/p9-g6-execution-log-run4-2026-04-28.md) — 22/22 PASS, bundle DDL-only 566 dòng, seed riêng 8 files.


1. Pre-execution: Canonical DDL Persist + Sanitize

Step 1.1 — Copy bundle từ /tmp ra persistent path

ssh contabo
mkdir -p /opt/incomex/data/tac/gate-a/
cp /tmp/g6_run4_20260428_041121/ddl/g6_execution_bundle.sql \
   /opt/incomex/data/tac/gate-a/g6_execution_bundle_dryrun.sql
sha256sum /opt/incomex/data/tac/gate-a/g6_execution_bundle_dryrun.sql

Expected SHA: c23987f29911e92d43d9ab6cb5e4fe77d64547785d98588859dfcc6f02997cad

Nếu SHA không khớp → STOP, report. /tmp có thể đã bị clear.

Step 1.2 — Controlled transform (KHÔNG blind sed)

Thứ tự: xóa scaffold TRƯỚC, thay tokens SAU.

cd /opt/incomex/data/tac/gate-a/

# Bước 1: Copy working file
cp g6_execution_bundle_dryrun.sql production_ddl_candidate.sql

# Bước 2: Xóa CREATE SCHEMA line (whitespace-tolerant — GPT R4#2)
sed -i '/^[[:space:]]*CREATE[[:space:]]\+SCHEMA.*p9_g6_dryrun/d' production_ddl_candidate.sql

# Bước 3: Xóa COMMENT ON SCHEMA line (whitespace-tolerant — GPT R4#2)
sed -i '/^[[:space:]]*COMMENT[[:space:]]\+ON[[:space:]]\+SCHEMA[[:space:]]\+p9_g6_dryrun/d' production_ddl_candidate.sql

# Bước 4: Thay exact tokens
sed -i 's/p9_g6_dryrun\./public\./g' production_ddl_candidate.sql
sed -i 's/search_path=p9_g6_dryrun, pg_catalog/search_path=public, pg_catalog/g' production_ddl_candidate.sql
sed -i 's/SET search_path TO p9_g6_dryrun, pg_catalog/SET search_path TO public, pg_catalog/g' production_ddl_candidate.sql

Step 1.3 — Verify transform (5 checks)

# V-T1: Không còn p9_g6_dryrun
grep -c 'p9_g6_dryrun' production_ddl_candidate.sql       # Expected: 0
# V-T2: Không có CREATE SCHEMA public
grep -c 'CREATE SCHEMA public' production_ddl_candidate.sql # Expected: 0
# V-T3: Có public. references
grep -c 'public\.' production_ddl_candidate.sql             # Expected: > 0
# V-T4: Không có DROP SCHEMA
grep -c 'DROP SCHEMA' production_ddl_candidate.sql          # Expected: 0
# V-T5: BEGIN/COMMIT nguyên vẹn
grep -c '^BEGIN' production_ddl_candidate.sql               # Expected: 1
grep -c '^COMMIT' production_ddl_candidate.sql              # Expected: 1

Bất kỳ FAIL → STOP, report.

Step 1.4 — DML Denylist + Classification (GPT R1#1 + R2#1 + R3#4)

Expected: 0 DML matches cho candidate này. G6 evidence confirms bundle = DDL-only, 6 trigger functions chỉ RAISE/gán NEW.*, không chứa INSERT INTO.

Scan (whitespace-tolerant regex — GPT R4#1, shell-safe — GPT R5#2):

grep -nE 'INSERT[[:space:]]+INTO'          production_ddl_candidate.sql || true
grep -nE '^[[:space:]]*COPY[[:space:]]'    production_ddl_candidate.sql || true
grep -nE '^[[:space:]]*UPDATE[[:space:]]'  production_ddl_candidate.sql || true
grep -nE '^[[:space:]]*DELETE[[:space:]]'  production_ddl_candidate.sql || true
grep -nE '^[[:space:]]*TRUNCATE[[:space:]]' production_ddl_candidate.sql || true

Decision tree:

  • 0 matches tổng cộng → PASS. Ghi "DML denylist: 0 matches, clean."
  • Bất kỳ match > 0 → Agent PHẢI:
    1. In line number + full context (±3 dòng)
    2. Classify: top-level / function-body / comment / DDL-clause
    3. Top-level DML → STOP. Gate A FAIL.
    4. Function-body hoặc comment DML → STOP. Unexpected cho candidate này. Escalate GPT/User review. Agent KHÔNG tự approve.

Step 1.5 — Compute production SHA

sha256sum /opt/incomex/data/tac/gate-a/production_ddl_candidate.sql

Ghi SHA mới vào action log. Production SHA ≠ dry-run SHA (do retarget + scaffold removal).

⚠️ Tạo production_ddl_candidate.sql chỉ là bước chuẩn bị. Gate A PASS chỉ được tuyên bố SAU KHI execution COMMIT thành công VÀ Q1–Q8 post-checks ALL PASS. (GPT R5#3)


2. Pre-checks

# Check Command Expected
P1 No tac_* tables docker exec postgres psql -U directus -d directus -t -c "SELECT count(*) FROM pg_tables WHERE schemaname='public' AND tablename LIKE 'tac_%'" 0
P2 fn_tac_* whitelist only docker exec postgres psql -U directus -d directus -t -c "SELECT array_agg(proname ORDER BY proname) FROM pg_proc JOIN pg_namespace n ON pronamespace=n.oid WHERE nspname='public' AND proname LIKE 'fn_tac_%'" {fn_tac_log_checker_issue}
P3 No trg_tac_* triggers docker exec postgres psql -U directus -d directus -t -c "SELECT count(*) FROM pg_trigger t JOIN pg_class c ON t.tgrelid=c.oid JOIN pg_namespace n ON c.relnamespace=n.oid WHERE n.nspname='public' AND t.tgname LIKE 'trg_tac_%'" 0
P4 Backup valid (PF-07 v0.5) age ≤30h, size ≥1MB, gzip valid, PG dump header PASS
P5 Directus healthy §2.1 OK / DB-accessible
P6 Candidate SHA sha256sum ...production_ddl_candidate.sql Matches §1.5
P7 Trigger guard healthy §2.2 (3 sub-checks) ALL PASS

Bất kỳ FAIL → STOP.

§2.1 P5 — Directus Health (token-safe, wording chính xác — GPT R3#5)

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
  https://vps.incomexsaigoncorp.vn/api/server/health)

if [ "$HTTP_CODE" = "200" ]; then
  echo "P5 PASS: Directus HTTP healthy"

elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
  # HTTP auth-protected — fallback: verify DB metadata accessible
  DB_CHECK=$(docker exec postgres psql -U directus -d directus -t -c \
    "SELECT count(*) FROM directus_collections LIMIT 1" 2>&1)
  if echo "$DB_CHECK" | grep -qE '^[[:space:]]*[0-9]+'; then
    echo "P5 PASS: HTTP health auth-protected, DB metadata accessible"
    echo "⚠️ LOG: HTTP health endpoint unverified (auth-protected). DB connectivity confirmed. Acceptable for Gate A."
  else
    echo "P5 FAIL: HTTP auth-protected AND DB metadata inaccessible"
    # STOP
  fi

elif [ "${HTTP_CODE:0:1}" = "5" ]; then
  echo "P5 FAIL: HTTP $HTTP_CODE — Directus server error"
  # STOP

else
  echo "P5 FAIL: HTTP $HTTP_CODE — unexpected"
  # STOP
fi

§2.2 P7 — Trigger Guard Deep Check (GPT R2#3 + R3#3)

Run #3 từng chết vì trigger guard search_path sai. Chỉ đếm event trigger = chưa đủ.

-- P7a: Event triggers exist + enabled
SELECT evtname, evtenabled
FROM pg_event_trigger
WHERE evtname LIKE 'evt_trigger_guard%';
-- Expected: 2 rows, evtenabled = 'O'

-- P7b: Guard function references QUALIFIED alert table
SELECT prosrc LIKE '%public.trigger_guard_alerts%' AS refs_qualified_table
FROM pg_proc WHERE proname = 'fn_evt_trigger_guard';
-- Expected: true (MUST be public.trigger_guard_alerts, not unqualified)

-- P7c: Guard function search_path = pg_catalog, public
SELECT proconfig
FROM pg_proc WHERE proname = 'fn_evt_trigger_guard';
-- Expected: contains 'search_path=pg_catalog, public'
-- MUST NOT contain p9_g6_dryrun (Run #3 lỗi này)

P7a/P7b/P7c bất kỳ FAIL → STOP. Repair trigger guard trước.


3. Execution

3.1 Chạy production DDL (GPT R3#1: pipefail)

set -o pipefail

docker exec -i postgres psql -U directus -d directus -v ON_ERROR_STOP=1 -X \
  < /opt/incomex/data/tac/gate-a/production_ddl_candidate.sql \
  2>&1 | tee /opt/incomex/data/tac/gate-a/execution-output.txt

PSQL_EXIT=${PIPESTATUS[0]}

if [ "$PSQL_EXIT" -ne 0 ]; then
  echo "EXECUTION ERROR: psql exit code $PSQL_EXIT"
  # → Go to §3.3 Error Path A
fi

Lưu ý:

  • Single transaction (BEGIN/COMMIT trong file)
  • ON_ERROR_STOP=1 — fail fast
  • Trigger guard emit WARNING cho 6 CREATE TRIGGER — expected, non-blocking
  • set -o pipefail + PIPESTATUS[0] đảm bảo không che exit code qua tee

3.2 Expected output (informational — KHÔNG phải source of truth)

Psql emit CREATE TABLE/INDEX/FUNCTION/TRIGGER/COMMIT. Post-check SQL counts (§4) là source of truth.

3.3 Error handling — 2 paths (GPT R3#2)

Path A — Execution ERROR (psql exit ≠ 0)

Transaction BEGIN/COMMIT + ON_ERROR_STOP → PG thường auto-rollback.

Bước 1 — Check auto-rollback:

T_COUNT=$(docker exec postgres psql -U directus -d directus -t -c \
  "SELECT count(*) FROM pg_tables WHERE schemaname='public' AND tablename LIKE 'tac_%'" | tr -d ' ')
F_LIST=$(docker exec postgres psql -U directus -d directus -t -c \
  "SELECT array_agg(proname ORDER BY proname) FROM pg_proc JOIN pg_namespace n ON pronamespace=n.oid WHERE nspname='public' AND proname LIKE 'fn_tac_%'" | tr -d ' ')
TRG_COUNT=$(docker exec postgres psql -U directus -d directus -t -c \
  "SELECT count(*) FROM pg_trigger t JOIN pg_class c ON t.tgrelid=c.oid JOIN pg_namespace n ON c.relnamespace=n.oid WHERE n.nspname='public' AND t.tgname LIKE 'trg_tac_%'" | tr -d ' ')

Bước 2 — Đánh giá:

  • tables=0 AND fn=chỉ {fn_tac_log_checker_issue} AND triggers=0 → Auto-rollback sạch. Log "auto-rollback clean." KHÔNG chạy manual rollback. → Report GATE A FAIL.
  • Partial objects remain → Chạy manual rollback (§5). → Report GATE A FAIL + rollback evidence.

Path B — COMMIT thành công nhưng Q1–Q8 FAIL (GPT R3#2)

Transaction đã COMMIT → objects tồn tại trong production. Phải manual rollback.

→ Chạy pre-rollback catalog verification (§5.0) → manual rollback (§5.1) → rollback verification (§5.2). → Report GATE A FAIL + rollback evidence.


4. Post-checks

# Check Command Expected
Q1 tac_* tables Same as P1 14
Q2 FK constraints docker exec postgres psql -U directus -d directus -t -c "SELECT count(*) FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY' AND table_schema='public' AND table_name LIKE 'tac_%'" 17
Q3 fn_tac_* total Same as P2 7: {fn_tac_birth_gate_lu, fn_tac_birth_gate_uv, fn_tac_enacted_immut, fn_tac_log_checker_issue, fn_tac_pm_consistency, fn_tac_pm_enacted_lock, fn_tac_uv_compute_derived}
Q4 trg_tac_* triggers Same as P3 6
Q5 Indexes on tac_* docker exec postgres psql -U directus -d directus -t -c "SELECT count(*) FROM pg_indexes WHERE schemaname='public' AND tablename LIKE 'tac_%'" 41
Q6 Directus healthy Same as P5 (§2.1) OK / DB-accessible
Q7 6 NEW fn SECURITY DEFINER docker exec postgres psql -U directus -d directus -t -c "SELECT proname, prosecdef FROM pg_proc JOIN pg_namespace n ON pronamespace=n.oid WHERE nspname='public' AND proname LIKE 'fn_tac_%' AND proname != 'fn_tac_log_checker_issue'" All 6 = t
Q8 6 NEW fn search_path locked docker exec postgres psql -U directus -d directus -t -c "SELECT proname, proconfig FROM pg_proc JOIN pg_namespace n ON pronamespace=n.oid WHERE nspname='public' AND proname LIKE 'fn_tac_%' AND proname != 'fn_tac_log_checker_issue'" All search_path=public, pg_catalog

ALL Q1–Q8 PASS → GATE A PASS. Any FAIL → §3.3 Path B (manual rollback).


5. Rollback

Khi nào chạy:

  • §3.3 Path A: chỉ nếu partial objects remain sau execution error
  • §3.3 Path B: sau COMMIT nhưng post-check fail

Step 5.0 — Pre-rollback catalog verification

-- Verify trigger names
SELECT tgname FROM pg_trigger t
  JOIN pg_class c ON t.tgrelid = c.oid
  JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = 'public' AND t.tgname LIKE 'trg_tac_%'
ORDER BY tgname;
-- Expected: trg_tac_birth_gate_lu, trg_tac_birth_gate_uv,
-- trg_tac_enacted_immut, trg_tac_pm_consistency,
-- trg_tac_pm_enacted_lock, trg_tac_uv_compute_derived

-- Verify function signatures (no-arg trigger functions)
SELECT proname, pg_get_function_identity_arguments(oid) AS args
FROM pg_proc JOIN pg_namespace n ON pronamespace = n.oid
WHERE nspname = 'public'
  AND proname LIKE 'fn_tac_%'
  AND proname != 'fn_tac_log_checker_issue'
ORDER BY proname;
-- Expected: all args = '' (empty)

Names/signatures không khớp → STOP, report discrepancy.

Step 5.1 — Rollback SQL

-- Triggers first
DROP TRIGGER IF EXISTS trg_tac_birth_gate_lu ON public.tac_logical_unit;
DROP TRIGGER IF EXISTS trg_tac_birth_gate_uv ON public.tac_unit_version;
DROP TRIGGER IF EXISTS trg_tac_enacted_immut ON public.tac_publication;
DROP TRIGGER IF EXISTS trg_tac_pm_consistency ON public.tac_publication_member;
DROP TRIGGER IF EXISTS trg_tac_pm_enacted_lock ON public.tac_publication_member;
DROP TRIGGER IF EXISTS trg_tac_uv_compute_derived ON public.tac_unit_version;

-- 6 NEW functions only (created by this Gate A run)
DROP FUNCTION IF EXISTS public.fn_tac_birth_gate_lu();
DROP FUNCTION IF EXISTS public.fn_tac_birth_gate_uv();
DROP FUNCTION IF EXISTS public.fn_tac_enacted_immut();
DROP FUNCTION IF EXISTS public.fn_tac_pm_consistency();
DROP FUNCTION IF EXISTS public.fn_tac_pm_enacted_lock();
DROP FUNCTION IF EXISTS public.fn_tac_uv_compute_derived();

-- Tables reverse FK order
DROP TABLE IF EXISTS public.tac_change_set_member CASCADE;
DROP TABLE IF EXISTS public.tac_publication_member CASCADE;
DROP TABLE IF EXISTS public.tac_change_set CASCADE;
DROP TABLE IF EXISTS public.tac_publication CASCADE;
DROP TABLE IF EXISTS public.tac_unit_version CASCADE;
DROP TABLE IF EXISTS public.tac_logical_unit CASCADE;
DROP TABLE IF EXISTS public.tac_birth_gate_config CASCADE;
DROP TABLE IF EXISTS public.tac_publication_type_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_section_type_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_cs_lifecycle_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_pub_lifecycle_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_review_state_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_uv_lifecycle_vocab CASCADE;
DROP TABLE IF EXISTS public.tac_lu_lifecycle_vocab CASCADE;

⚠️ TUYỆT ĐỐI KHÔNG DROP public.fn_tac_log_checker_issue — pre-existing E-R3.

Step 5.2 — Rollback verification

# P1 → 0 tables, P2 → whitelist only, P3 → 0 triggers, P5 → healthy

6. Hard Exclusions

# Cấm
HE-1 Không top-level DML (INSERT/UPDATE/DELETE/COPY/TRUNCATE)
HE-2 Không seed (Gate C scope)
HE-3 Không Directus collection registration (Gate B scope)
HE-4 Không role/policy/permission (G8B scope)
HE-5 Không token provisioning
HE-6 Không cron setup
HE-7 Không modify fn_tac_log_checker_issue (pre-existing E-R3)
HE-8 Không modify existing objects ngoài tac_*
HE-9 Không DROP pre-existing objects. Rollback chỉ DROP objects tạo bởi Gate A run hiện tại. Tuyệt đối không DROP fn_tac_log_checker_issue.
HE-10 Không chạy file nào khác ngoài production_ddl_candidate.sql
HE-11 Không chạy lệnh trên local Mac — tất cả qua SSH VPS contabo

7. Directus API Access Safety

  • Token retrieval runtime-only, masked
  • Không print token/Auth header/env secret
  • Directus unhealthy (§2.1 both paths fail) → STOP
  • Không hỏi User paste secrets

8. Action Log

Path: knowledge/dev/laws/dieu38-trien-khai/reports/p9-gate-a-production-ddl-execution-log-YYYY-MM-DD.md

No-overwrite policy (GPT R4#3): Nếu path đã tồn tại, tạo suffix -run2, -run3, hoặc timestamp. KHÔNG overwrite log cũ.

Nội dung bắt buộc:

  • Timestamp start/end
  • Candidate path: /opt/incomex/data/tac/gate-a/production_ddl_candidate.sql
  • Dry-run bundle SHA (§1.1)
  • Production DDL candidate SHA (§1.5)
  • Transform summary: 5 sed steps, 5 verify results
  • DML classification: total matches, per-line context + classification (hoặc "0 matches, clean")
  • Pre-checks P1–P7 (incl. P5 health method + P7 3 sub-checks)
  • psql exit code + full output
  • Post-checks Q1–Q8
  • Trigger guard warnings (expected count)
  • Error handling path taken (none / Path A auto-clean / Path A manual / Path B manual)
  • PASS/FAIL verdict
  • Rollback evidence (nếu có)

9. Output Format

Gate A Production DDL Execution Results:

Context:
  VPS: contabo (vmi3080463)
  Candidate: /opt/incomex/data/tac/gate-a/production_ddl_candidate.sql

Pre-execution:
  Bundle SHA (dry-run): [hash] (verified/MISSING)
  Transform: 5 steps → V-T1–V-T5 [ALL PASS / FAIL detail]
  DML denylist: [0 matches clean / N matches → classification]
  Production SHA: [hash]

Pre-checks: P1–P7 [ALL PASS / FAIL detail]
  P5: [HTTP 200 / auth-protected+DB-accessible / FAIL]
  P7: [P7a enabled / P7b public.trigger_guard_alerts / P7c search_path]

Execution:
  psql exit: [0 / non-zero]
  Output: /opt/incomex/data/tac/gate-a/execution-output.txt

Error handling: [none / Path A auto-clean / Path A manual rollback / Path B manual rollback]

Post-checks: Q1–Q8 [ALL PASS / FAIL detail]

Verdict: GATE A PASS / GATE A FAIL

10. Sau Gate A PASS

STOP. Không tự chuyển sang Gate B/C. Báo cáo → GPT review → User authorize Gate B riêng.


Appendix A — Patch Log

Rev GPT Patches Key
v0.1→v0.2 R1 5 DML denylist, controlled transform, catalog verify, output≠truth, fn signature
v0.2→v0.3 R2 6 DML classify, Directus token-safe, trigger guard deep, auto-rollback, HE-9, action log
v0.3→v0.4 R3 6 pipefail, 2 rollback paths, P7b qualified, DML expected=0 escalate, health wording, VPS explicit
v0.4→v0.5 R4+R5 3+3 DML grep whitespace-tolerant, scaffold sed robust regex, action log no-overwrite, heading fix, grep `

v0.3→v0.4 detail:

# GPT R3 Patch
1 tee masks exit code §3.1: set -o pipefail + PIPESTATUS[0]
2 Tách rollback paths §3.3: Path A (execution error) vs Path B (COMMIT + Q-fail)
3 P7b qualified reference §2.2 P7b: public.trigger_guard_alerts not unqualified
4 DML expected=0, unexpected→STOP §1.4: any match = escalate, agent không tự approve
5 Health wording §2.1: HTTP 200 / auth+DB-accessible / 5xx STOP
6 VPS context Header + HE-11 mới

v0.4→v0.5 detail:

# GPT R4 Patch
1 DML grep miss indented lines §1.4: [[:space:]]* prefix + [[:space:]] suffix, INSERT[[:space:]]+INTO unanchored
2 Scaffold sed miss IF NOT EXISTS / indent §1.2: whitespace-tolerant regex cho CREATE SCHEMA + COMMENT ON SCHEMA
3 Action log overwrite risk §8: no-overwrite policy, suffix -run2 hoặc timestamp

v0.5 GPT R5 final patches:

# GPT R5 Patch
1 Heading still said v0.4 Title → v0.5 FINAL
2 grep no-match exit 1 aborts strict shell §1.4: `
3 Candidate generation ≠ Gate A PASS §1.5: clarification block added

Gate A Production DDL Execution Prompt Design v0.5 FINAL | S186 | 2026-04-28 GPT R5 CONDITIONAL PASS — all final edits applied Chờ User GO → Agent execute KHÔNG authorize. KHÔNG Gate B/C/G8B/G11/P9.