P9 G6 — Migration Dry-Run Package v0.2
P9 G6 — Migration Dry-Run Package
Loại: Migration dry-run execution package — G6 of P9 Entry Gate Tier 3 Phase: P9 Entry Gate, Tier 3, Gate G6 Trạng thái: DRAFT v0.2 — patches v0.1 theo GPT R1 review (6 patches). Chờ GPT R2. Phiên: S184 | Ngày: 2026-04-27 Agent soạn: Opus 4.7 (Desktop) Inputs đã verify: P5 v0.2, P5b v0.2 (DEFER), P6 v0.2, P7 v0.2, P8 v0.4, Tier 3 Readiness, P9-G6-source-extraction-note v0.1, GPT R1 review (
reports/gpt-review-g6-dry-run-package-r1-2026-04-27.md) Operating model: User = mục tiêu/yes-no. Opus = điều hành + soạn. GPT = giám sát + phản biện.
CẢNH BÁO LỚN: Package này là plan + DDL design, KHÔNG phải execution prompt cho agent. Sau khi GPT + User PASS package, sẽ soạn execution prompt riêng giao agent thực thi trên VPS.
1. Mục tiêu G6
G6 = Migration Dry-Run trên schema isolated p9_g6_dryrun.
Chứng minh ba điều:
- Schema design 14 tables (P8 §1.4) compile được trên PostgreSQL 16 thật, không lỗi cú pháp/FK/CHECK.
- Functions/triggers cần thiết compile được (skeleton + behavior-critical, không cần test runtime đầy đủ).
- Rollback
DROP SCHEMA p9_g6_dryrun CASCADEđể lại 0 residue trong production.
Sau khi G6 PASS → mở G8 (Directus roles tac-agent/tac-admin) → G11 (User approval) → P9 production migration thật.
2. Scope / Out-of-scope
2.1 Trong scope G6
- CREATE SCHEMA
p9_g6_dryrun - CREATE 14 tables trong
p9_g6_dryrun.*(production-faithful theo P8 §1.1 hardening: FK vocab thay CHECK enum) - CREATE 6 functions/triggers compile + behavior-critical trong
p9_g6_dryrun.* - Seed full vocab values vào 7 vocab tables + 1 config table (61 rows total)
- Verification plan: DDL exists, FK/CHECK constraints bind, function signature OK, trigger ATTACH OK, seed SHA-256 khớp KB
- Constraint behavior smoke (test 1 violation per major constraint type — exist + violation cả hai)
- Rollback test: DROP CASCADE + verify 0 residue
- Action log: KB markdown report + local artifacts only (KHÔNG ghi
dot_action_logtable — patched v0.2 GPT R1#3) - Pre-flight checks (schema chưa tồn tại, public schema sạch tac_ mọi loại object* — patched v0.2 GPT R1#4, FAC-07/08/09 sẵn, 19 DOTs sẵn, fn_tac_log_checker_issue sẵn, backup tươi)
2.2 Out-of-scope G6 (CẤM tuyệt đối)
- ❌ CREATE/ALTER/DROP
public.tac_*(production, mọi loại object) - ❌ CREATE/MODIFY Directus roles (
tac-agent,tac-admin) — thuộc G8 - ❌ CREATE/MODIFY Directus collections — thuộc G8
- ❌ Activate cron jobs cho DOT-TAC-* — thuộc P9 production
- ❌ INSERT vào
taxonomy_labels/entity_labels - ❌ INSERT vào
system_issues(kể cả quafn_tac_log_checker_issue) - ❌ INSERT vào
dot_action_log(production audit table — patched v0.2 GPT R1#3) - ❌ Cleanup
_dot_originfield - ❌ Behavior test functions/triggers ngoài V3 smoke (chỉ compile + smoke)
- ❌ Test runtime trên 76 sandbox units
- ❌ Mọi mutation lên
public.*ngoài bản thân DROP SCHEMA p9_g6_dryrun - ❌ Modify
dot_tools/taxonomy_facets - ❌ Sửa GSM secrets
- ❌ CREATE EXTENSION pgcrypto (DB-level mutation — patched v0.2 GPT R1#2). G6 dùng built-in
pg_catalog.sha256()thay digest().
3. Target schema design
3.1 Schema name + lý do
p9_g6_dryrun — chọn schema mới, KHÔNG reuse sandbox_tac.
| Tiêu chí | p9_g6_dryrun (mới) |
sandbox_tac (reuse) |
|---|---|---|
| Sạch, zero residue trước run | ✅ | ❌ Có 76 units từ P7 |
| Naming rõ phase | ✅ p9.g6.dryrun | ❌ generic |
| ROLLBACK đơn giản | ✅ DROP SCHEMA CASCADE | ❌ Cần truncate selective |
| Verify 0 residue dễ | ✅ schema biến mất | ❌ Phải so trước/sau |
| Risk write nhầm | Thấp (schema không tồn tại trước) | Trung bình (đã có data) |
3.2 Naming convention trong p9_g6_dryrun.*
Tên bảng/function giữ ĐÚNG như production target (tac_*) — vì G6 mục đích chứng minh DDL production-ready. Khác biệt chỉ ở schema prefix:
- G6:
p9_g6_dryrun.tac_logical_unit - Production target:
public.tac_logical_unit
Mọi DDL statement BẮT BUỘC schema-qualify đầy đủ. Không bao giờ chạy CREATE TABLE tac_* (không có schema prefix) — vì search_path có thể lệch dẫn tới ghi nhầm public.
3.3 Search path
Connection setup cho dry-run:
SET search_path TO p9_g6_dryrun, pg_catalog;
-- Loại bỏ public khỏi search_path để mọi unqualified reference fail-loud.
-- pg_catalog vẫn cần cho built-in functions (sha256, encode, convert_to, gen_random_uuid, etc.)
4. Pre-flight checks (MANDATORY trước khi chạy DDL)
Theo bài học S183 AP-DRY-RUN-FK + handoff §3.3 + GPT R1 #4 (mở rộng public conflict check).
4.1 Pre-flight checks list
| # | Check | SQL/Method | Expected | If FAIL |
|---|---|---|---|---|
| PF-01 | Schema p9_g6_dryrun chưa tồn tại |
SELECT count(*) FROM information_schema.schemata WHERE schema_name='p9_g6_dryrun'; |
0 | DROP SCHEMA p9_g6_dryrun CASCADE → re-check |
| PF-02 | Production public.* không có tac_* object thuộc bất kỳ loại (tables/views/sequences/types/functions/triggers, trừ whitelisted) — patched v0.2 GPT R1#4 |
Query gộp UNION ALL (xem §4.1.1 dưới) | 0 rows, NULL array | STOP — production conflict, escalate |
| PF-03 | pg_catalog.sha256 built-in tồn tại (PG 11+) — patched v0.2 GPT R1#2 |
SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE n.nspname='pg_catalog' AND p.proname='sha256'; |
≥ 1 (PG 16 có built-in) | STOP — version PG quá cũ, escalate |
| PF-04 | FAC-07/08/09 sẵn trong production taxonomy_facets |
Directus API GET /items/taxonomy_facets?filter[code][_in]=FAC-07,FAC-08,FAC-09&filter[status][_eq]=active |
3 rows, ids 8/9/10 | STOP — Đ24 dependency missing |
| PF-05 | 19 DOT-TAC-* sẵn trong dot_tools |
Directus API GET /items/dot_tools?filter[code][_starts_with]=DOT-TAC-&filter[status][_eq]=active |
19 rows, ids 971–989 | STOP — Đ35 dependency missing |
| PF-06 | public.fn_tac_log_checker_issue sẵn (whitelisted E-R3 artifact) |
SELECT proname, prosecdef FROM pg_proc WHERE proname='fn_tac_log_checker_issue' AND pronamespace='public'::regnamespace; |
1 row, prosecdef=true | STOP — checker adapter missing |
| PF-07 | Backup tươi (< 6h) | ls -la /opt/incomex/backup/postgres-*.sql.gz | tail -1 (Bash via SSH) hoặc rclone lsl GDrive:incomex-backup/ |
mtime < 6h ago | STOP — chạy backup tay trước |
| PF-08 | TRIGGER-GUARD informational mode | Verify event triggers permit CREATE SCHEMA | Permits | STOP — guard configuration issue |
| PF-09 | Connection identity hợp lệ qua .env (KHÔNG hardcode user) — patched v0.2 GPT R1#5 |
psql "$(cat .env_db_url)" -c 'SELECT current_user, current_database();' → verify user có CREATE SCHEMA permission, db=directus |
user thuộc danh sách hợp lệ (workflow_admin, hoặc admin-tier user); db=directus | STOP — wrong connection / insufficient permission |
| PF-10 | Search path verify | SHOW search_path; |
Set theo §3.3 trước run | Adjust |
4.1.1 PF-02 — Mở rộng public conflict check (full SQL)
-- PF-02: No tac_* objects in public schema (except whitelisted fn_tac_log_checker_issue)
-- Patched v0.2 GPT R1#4
WITH tac_objects_in_public AS (
SELECT 'table' AS object_type, table_name AS object_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name LIKE 'tac\_%' ESCAPE '\'
AND table_type = 'BASE TABLE'
UNION ALL
SELECT 'view', table_name
FROM information_schema.views
WHERE table_schema = 'public'
AND table_name LIKE 'tac\_%' ESCAPE '\'
UNION ALL
SELECT 'sequence', sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
AND sequence_name LIKE 'tac\_%' ESCAPE '\'
UNION ALL
SELECT 'type', t.typname
FROM pg_type t
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'public'
AND t.typname LIKE 'tac\_%' ESCAPE '\'
AND t.typtype IN ('c', 'e', 'd') -- composite, enum, domain
UNION ALL
SELECT 'function', p.proname
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public'
AND p.proname LIKE 'fn_tac\_%' ESCAPE '\'
AND p.proname != 'fn_tac_log_checker_issue' -- whitelisted (E-R3)
UNION ALL
SELECT 'trigger', tg.tgname
FROM pg_trigger tg
JOIN pg_class c ON c.oid = tg.tgrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND tg.tgname LIKE 'trg_tac\_%' ESCAPE '\'
AND NOT tg.tgisinternal
)
SELECT
count(*) AS total_violations,
array_agg(object_type || ':' || object_name ORDER BY object_type, object_name) AS objects
FROM tac_objects_in_public;
-- Expected: total_violations = 0, objects = NULL
-- If total_violations > 0 → STOP, log objects, escalate
Whitelist: Chỉ public.fn_tac_log_checker_issue (E-R3 production artifact, S183).
4.2 Pre-flight FK vocabulary verify (AP-DRY-RUN-FK mitigation)
Trước khi tạo CORE tables tham chiếu vocab tables, verify mỗi FK target sau seed:
| Core column | FK target | Verify |
|---|---|---|
tac_logical_unit.lifecycle_status |
tac_lu_lifecycle_vocab(code) |
Sau seed §7.2, query vocab table count = 3 (active/draft_only/retired) |
tac_logical_unit.section_type |
tac_section_type_vocab(code) |
Sau seed §7.2, count = 17 |
tac_unit_version.lifecycle_status |
tac_uv_lifecycle_vocab(code) |
count = 4 |
tac_unit_version.review_state |
tac_review_state_vocab(code) |
count = 5 |
tac_publication.lifecycle_status |
tac_pub_lifecycle_vocab(code) |
count = 4 |
tac_publication.publication_type |
tac_publication_type_vocab(code) |
count = 10 |
tac_change_set.lifecycle_status |
tac_cs_lifecycle_vocab(code) |
count = 7 |
tac_publication_member.publication_id |
tac_publication(id) |
Empty (no rows yet) — chỉ verify FK constraint exists |
tac_publication_member.unit_version_id |
tac_unit_version(id) |
Empty — verify FK exists |
tac_change_set_member.change_set_id |
tac_change_set(id) |
Empty — verify FK exists |
5. DDL plan — 14 tables (production-faithful)
Quy tắc: Mọi DDL dùng
p9_g6_dryrun.{table}. KHÔNG bao giờ unqualified.
5.1 Schema bootstrap
-- Pre-flight: assume PF-01→PF-10 passed
CREATE SCHEMA p9_g6_dryrun;
COMMENT ON SCHEMA p9_g6_dryrun IS
'P9 Gate G6 dry-run isolated schema. ROLLBACK: DROP SCHEMA p9_g6_dryrun CASCADE. Phiên S184. NOT FOR PRODUCTION.';
SET search_path TO p9_g6_dryrun, pg_catalog;
5.2 Group A — 5 lifecycle vocab tables
-- A1. tac_lu_lifecycle_vocab
CREATE TABLE p9_g6_dryrun.tac_lu_lifecycle_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- A2. tac_uv_lifecycle_vocab
CREATE TABLE p9_g6_dryrun.tac_uv_lifecycle_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- A3. tac_review_state_vocab
CREATE TABLE p9_g6_dryrun.tac_review_state_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- A4. tac_pub_lifecycle_vocab
CREATE TABLE p9_g6_dryrun.tac_pub_lifecycle_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- A5. tac_cs_lifecycle_vocab
CREATE TABLE p9_g6_dryrun.tac_cs_lifecycle_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
5.3 Group B — 2 type vocab tables
-- B1. tac_section_type_vocab (per P5 §5.5)
CREATE TABLE p9_g6_dryrun.tac_section_type_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
lifecycle_status TEXT NOT NULL DEFAULT 'active'
CHECK (lifecycle_status IN ('active', 'deprecated', 'retired')),
owner TEXT,
soft_limit_words INTEGER NOT NULL DEFAULT 500 CHECK (soft_limit_words > 0),
hard_limit_words INTEGER NOT NULL DEFAULT 1500 CHECK (hard_limit_words > soft_limit_words),
description_required BOOLEAN NOT NULL DEFAULT TRUE,
body_required BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- B2. tac_publication_type_vocab (per P5 §5.6)
CREATE TABLE p9_g6_dryrun.tac_publication_type_vocab (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
lifecycle_status TEXT NOT NULL DEFAULT 'active'
CHECK (lifecycle_status IN ('active', 'deprecated', 'retired')),
default_risk_tier TEXT NOT NULL DEFAULT 'medium'
CHECK (default_risk_tier IN ('low', 'medium', 'high', 'highest')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
5.4 Group C — 1 config table
-- C1. tac_birth_gate_config (per P8 §2.3)
CREATE TABLE p9_g6_dryrun.tac_birth_gate_config (
checker_id TEXT PRIMARY KEY,
-- Ví dụ: 'BG-LU-02', 'BG-UV-04'.
mode TEXT NOT NULL DEFAULT 'block'
CHECK (mode IN ('block', 'warn')),
enabled BOOLEAN NOT NULL DEFAULT TRUE,
rationale TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
5.5 Group D — 4 core tables
-- D1. tac_logical_unit (per P5 §5.1, P8 §1.1 hardening)
CREATE TABLE p9_g6_dryrun.tac_logical_unit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_address TEXT NOT NULL UNIQUE,
-- INV-ADDRESS-IMMUT. Format: D38-{DOC}-{(ROOT|S{n}-P{n}...)}
-- Regex enforced by birth gate fn_tac_birth_gate_lu, KHÔNG CHECK constraint
-- (vì regex P8 §5.2 D38-local có thể evolve).
doc_code TEXT NOT NULL,
-- Structural binding, KHÔNG FK (P5 DP-1, OD-P5-04).
parent_id UUID REFERENCES p9_g6_dryrun.tac_logical_unit(id),
sort_order INTEGER NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
section_type TEXT NOT NULL
REFERENCES p9_g6_dryrun.tac_section_type_vocab(code),
section_code TEXT,
owner TEXT NOT NULL,
identity_profile JSONB NOT NULL DEFAULT '{}'::jsonb,
tier TEXT,
-- DERIVED, agent KHÔNG khai tay.
lifecycle_status TEXT NOT NULL DEFAULT 'draft_only'
REFERENCES p9_g6_dryrun.tac_lu_lifecycle_vocab(code),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_tac_lu_doc_code ON p9_g6_dryrun.tac_logical_unit(doc_code);
CREATE INDEX idx_tac_lu_parent ON p9_g6_dryrun.tac_logical_unit(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_tac_lu_section_type ON p9_g6_dryrun.tac_logical_unit(section_type);
CREATE INDEX idx_tac_lu_lifecycle ON p9_g6_dryrun.tac_logical_unit(lifecycle_status);
CREATE INDEX idx_tac_lu_identity_profile_gin ON p9_g6_dryrun.tac_logical_unit USING GIN (identity_profile);
COMMENT ON TABLE p9_g6_dryrun.tac_logical_unit IS
'Logical unit identity (P5 §5.1 + P8 §1.1 hardening). G6 dry-run only.';
-- D2. tac_unit_version (per P5 §5.2, P8 §1.1 hardening)
CREATE TABLE p9_g6_dryrun.tac_unit_version (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
logical_unit_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_logical_unit(id),
version_number INTEGER NOT NULL DEFAULT 1 CHECK (version_number > 0),
title TEXT NOT NULL,
body TEXT,
description TEXT,
content_hash TEXT,
-- DERIVED by fn_tac_uv_compute_derived (BEFORE INSERT/UPDATE).
-- Algorithm: pg_catalog.sha256(convert_to(...)) — built-in PG 11+.
-- KHÔNG dùng pgcrypto digest() để tránh extension dependency (v0.2 GPT R1#2).
lifecycle_status TEXT NOT NULL DEFAULT 'draft'
REFERENCES p9_g6_dryrun.tac_uv_lifecycle_vocab(code),
review_state TEXT NOT NULL DEFAULT 'unreviewed'
REFERENCES p9_g6_dryrun.tac_review_state_vocab(code),
-- Length flag GIỮ CHECK (P8 §1.1 chú thích "Cố định technical")
length_flag TEXT NOT NULL DEFAULT 'normal'
CHECK (length_flag IN ('normal', 'soft_limit', 'hard_limit')),
length_exception_reason TEXT,
content_profile JSONB NOT NULL DEFAULT '{}'::jsonb,
editor TEXT,
provenance TEXT NOT NULL DEFAULT 'PROV-AI',
-- Verify FAC-PROV vocab via DOT-TAC-LABEL-FACET-VERIFY post-G6.
-- Vector projection hooks (P5 §5.2)
-- vector_sync_status GIỮ CHECK 4 values (P5 enum hiện hành).
-- 'skip' từ P5b cần amend P5 trước khi thêm — out of G6 scope.
vector_sync_status TEXT NOT NULL DEFAULT 'pending'
CHECK (vector_sync_status IN ('pending', 'synced', 'stale', 'error')),
vector_synced_at TIMESTAMPTZ,
vector_chunk_count INTEGER NOT NULL DEFAULT 0 CHECK (vector_chunk_count >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
enacted_at TIMESTAMPTZ,
UNIQUE (logical_unit_id, version_number)
);
CREATE INDEX idx_tac_uv_logical_unit ON p9_g6_dryrun.tac_unit_version(logical_unit_id);
CREATE INDEX idx_tac_uv_lifecycle ON p9_g6_dryrun.tac_unit_version(lifecycle_status);
CREATE INDEX idx_tac_uv_review_state ON p9_g6_dryrun.tac_unit_version(review_state);
CREATE INDEX idx_tac_uv_vector_sync ON p9_g6_dryrun.tac_unit_version(vector_sync_status)
WHERE vector_sync_status != 'synced';
CREATE INDEX idx_tac_uv_content_profile_gin ON p9_g6_dryrun.tac_unit_version USING GIN (content_profile);
COMMENT ON TABLE p9_g6_dryrun.tac_unit_version IS
'Unit version content (P5 §5.2 + P8 §1.1 hardening). G6 dry-run only.';
-- D3. tac_publication (per P5 §5.3, P8 §1.1 hardening)
CREATE TABLE p9_g6_dryrun.tac_publication (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_code TEXT NOT NULL,
version TEXT NOT NULL,
publication_type TEXT NOT NULL
REFERENCES p9_g6_dryrun.tac_publication_type_vocab(code),
name TEXT NOT NULL,
owner TEXT NOT NULL,
description TEXT,
lifecycle_status TEXT NOT NULL DEFAULT 'proposed'
REFERENCES p9_g6_dryrun.tac_pub_lifecycle_vocab(code),
enacted_at TIMESTAMPTZ,
council_score NUMERIC,
approved_by TEXT,
risk_tier TEXT NOT NULL DEFAULT 'medium'
CHECK (risk_tier IN ('low', 'medium', 'high', 'highest')),
publication_profile JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (doc_code, version)
);
CREATE INDEX idx_tac_pub_doc_code ON p9_g6_dryrun.tac_publication(doc_code);
CREATE INDEX idx_tac_pub_lifecycle ON p9_g6_dryrun.tac_publication(lifecycle_status);
CREATE INDEX idx_tac_pub_publication_type ON p9_g6_dryrun.tac_publication(publication_type);
CREATE INDEX idx_tac_pub_publication_profile_gin ON p9_g6_dryrun.tac_publication USING GIN (publication_profile);
COMMENT ON TABLE p9_g6_dryrun.tac_publication IS
'Publication record (P5 §5.3 + P8 §1.1 hardening). G6 dry-run only.';
-- D4. tac_publication_member (per P5 §5.4)
CREATE TABLE p9_g6_dryrun.tac_publication_member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
publication_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_publication(id),
logical_unit_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_logical_unit(id),
unit_version_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_unit_version(id),
render_order INTEGER NOT NULL DEFAULT 0 CHECK (render_order >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (publication_id, logical_unit_id)
);
CREATE INDEX idx_tac_pm_publication ON p9_g6_dryrun.tac_publication_member(publication_id);
CREATE INDEX idx_tac_pm_unit_version ON p9_g6_dryrun.tac_publication_member(unit_version_id);
CREATE INDEX idx_tac_pm_logical_unit ON p9_g6_dryrun.tac_publication_member(logical_unit_id);
-- NOTE (v0.2 GPT R1#1): Partial index on enacted publications optimization DEFERRED.
-- Rationale: PG không cho subquery trong WHERE của partial index predicate.
-- Enacted lock được enforce bằng trigger fn_tac_pm_enacted_lock (§6.6), không cần partial index.
-- Nếu future cần optimize query enacted-only → thêm denormalized column hoặc materialized view.
COMMENT ON TABLE p9_g6_dryrun.tac_publication_member IS
'Junction publication ↔ unit_version (P5 §5.4). G6 dry-run only.';
5.6 Group E — 2 change-set hook tables
-- E1. tac_change_set (per P5 §5.7, P8 §1.1 hardening)
CREATE TABLE p9_g6_dryrun.tac_change_set (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
publication_id UUID REFERENCES p9_g6_dryrun.tac_publication(id),
scope_description TEXT NOT NULL,
lifecycle_status TEXT NOT NULL DEFAULT 'draft'
REFERENCES p9_g6_dryrun.tac_cs_lifecycle_vocab(code),
apr_ref TEXT,
owner TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
submitted_at TIMESTAMPTZ,
enacted_at TIMESTAMPTZ
);
CREATE INDEX idx_tac_cs_publication ON p9_g6_dryrun.tac_change_set(publication_id) WHERE publication_id IS NOT NULL;
CREATE INDEX idx_tac_cs_lifecycle ON p9_g6_dryrun.tac_change_set(lifecycle_status);
-- Partial index per P8 §1.2: lifecycle_status='enacted' (HỢP LỆ — predicate constant, không subquery)
CREATE INDEX idx_tac_cs_lifecycle_enacted ON p9_g6_dryrun.tac_change_set(id)
WHERE lifecycle_status = 'enacted';
-- E2. tac_change_set_member (per P5 §5.8)
CREATE TABLE p9_g6_dryrun.tac_change_set_member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
change_set_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_change_set(id),
logical_unit_id UUID NOT NULL REFERENCES p9_g6_dryrun.tac_logical_unit(id),
change_type TEXT NOT NULL
CHECK (change_type IN ('create', 'new_version', 'retire', 'structural')),
old_version_id UUID REFERENCES p9_g6_dryrun.tac_unit_version(id),
new_version_id UUID REFERENCES p9_g6_dryrun.tac_unit_version(id),
snapshot_data JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (change_set_id, logical_unit_id)
);
CREATE INDEX idx_tac_csm_change_set ON p9_g6_dryrun.tac_change_set_member(change_set_id);
CREATE INDEX idx_tac_csm_logical_unit ON p9_g6_dryrun.tac_change_set_member(logical_unit_id);
5.7 Tổng số object DDL group
| Group | Tables | Indexes | UNIQUE constraints | FK constraints | CHECK constraints |
|---|---|---|---|---|---|
| A (5 lifecycle vocab) | 5 | 0 (PK chỉ) | 5 PK | 0 | 5 (sort_order >= 0) |
| B (2 type vocab) | 2 | 0 (PK chỉ) | 2 PK | 0 | 7 (lifecycle, risk_tier, soft/hard_limit, etc.) |
| C (1 config) | 1 | 0 | 1 PK | 0 | 1 (mode) |
| D (4 core) | 4 | 13 + 4 GIN/PK | 4 PK + 1 (uv unique) + 1 (pm unique) | 8 (FK chain) | ~10 (CHECK riêng) |
| E (2 change-set) | 2 | 4 (gồm 1 partial hợp lệ) | 2 PK + 1 (csm unique) | 4 (FK chain) | 1 (change_type) |
| Tổng | 14 | ~21 | ~17 | ~12 | ~24 |
Patched v0.2 GPT R1#1: Bỏ invalid partial index idx_tac_pm_pub_enacted. Tổng index giảm 1 (từ ~22 xuống ~21). Partial idx_tac_cs_lifecycle_enacted giữ vì predicate lifecycle_status='enacted' là constant, hợp lệ.
6. Functions/triggers — compile + behavior-critical
Patched v0.2 GPT R1#2: Tất cả function dùng built-in
pg_catalog.sha256(bytea)(PG 11+) thay vìpgcrypto.digest(). Không cần pgcrypto extension. Schema-qualifiedpg_catalog.*để search_path không lệch.
6.1 fn_tac_uv_compute_derived
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_uv_compute_derived()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
DECLARE
v_word_count INTEGER;
v_soft_limit INTEGER;
v_hard_limit INTEGER;
BEGIN
-- Compute content_hash (sha256 của title+body+description+content_profile)
-- Built-in pg_catalog.sha256(bytea) — không cần pgcrypto extension (v0.2 GPT R1#2)
NEW.content_hash := pg_catalog.encode(
pg_catalog.sha256(
pg_catalog.convert_to(
coalesce(NEW.title, '') || '|' ||
coalesce(NEW.body, '') || '|' ||
coalesce(NEW.description, '') || '|' ||
coalesce(NEW.content_profile::text, '{}'),
'UTF8'
)
),
'hex'
);
-- Compute length_flag (theo section_type thresholds)
SELECT st.soft_limit_words, st.hard_limit_words
INTO v_soft_limit, v_hard_limit
FROM p9_g6_dryrun.tac_section_type_vocab st
JOIN p9_g6_dryrun.tac_logical_unit lu
ON lu.section_type = st.code
WHERE lu.id = NEW.logical_unit_id;
v_word_count := pg_catalog.array_length(
pg_catalog.regexp_split_to_array(coalesce(NEW.body, ''), '\s+'),
1
);
NEW.length_flag := CASE
WHEN v_word_count IS NULL OR v_word_count = 0 THEN 'normal'
WHEN v_word_count > v_hard_limit THEN 'hard_limit'
WHEN v_word_count > v_soft_limit THEN 'soft_limit'
ELSE 'normal'
END;
-- updated_at auto
NEW.updated_at := now();
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_tac_uv_compute_derived
BEFORE INSERT OR UPDATE
ON p9_g6_dryrun.tac_unit_version
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_uv_compute_derived();
6.2 fn_tac_birth_gate_lu (BG-LU-01→06 skeleton + behavior-critical)
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_birth_gate_lu()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
DECLARE
v_section_active BOOLEAN;
v_parent_doc_code TEXT;
BEGIN
-- BG-LU-01: canonical_address regex (D38-local contract per P8 §5.2)
-- Regex: ^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$
IF NEW.canonical_address !~ '^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$' THEN
RAISE EXCEPTION 'BG-LU-01: canonical_address % không khớp regex D38-local', NEW.canonical_address
USING ERRCODE = 'check_violation';
END IF;
-- BG-LU-02: doc_code valid — deferred OD-P5-04, hiện chỉ check non-null
IF NEW.doc_code IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.doc_code)) = 0 THEN
RAISE EXCEPTION 'BG-LU-02: doc_code rỗng' USING ERRCODE = 'not_null_violation';
END IF;
-- BG-LU-03: parent same doc_code
IF NEW.parent_id IS NOT NULL THEN
SELECT doc_code INTO v_parent_doc_code
FROM p9_g6_dryrun.tac_logical_unit
WHERE id = NEW.parent_id;
IF v_parent_doc_code IS NULL THEN
RAISE EXCEPTION 'BG-LU-03: parent_id % không tồn tại', NEW.parent_id
USING ERRCODE = 'foreign_key_violation';
END IF;
IF v_parent_doc_code != NEW.doc_code THEN
RAISE EXCEPTION 'BG-LU-03: parent doc_code % != child doc_code %',
v_parent_doc_code, NEW.doc_code
USING ERRCODE = 'check_violation';
END IF;
END IF;
-- BG-LU-04: section_type active
SELECT (lifecycle_status = 'active') INTO v_section_active
FROM p9_g6_dryrun.tac_section_type_vocab
WHERE code = NEW.section_type;
IF NOT coalesce(v_section_active, FALSE) THEN
RAISE EXCEPTION 'BG-LU-04: section_type % không active', NEW.section_type
USING ERRCODE = 'check_violation';
END IF;
-- BG-LU-05: owner present (covered by NOT NULL constraint, redundant safety)
IF NEW.owner IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.owner)) = 0 THEN
RAISE EXCEPTION 'BG-LU-05: owner rỗng' USING ERRCODE = 'not_null_violation';
END IF;
-- BG-LU-06: sort_order valid (covered by CHECK >= 0, redundant safety)
IF NEW.sort_order IS NULL OR NEW.sort_order < 0 THEN
RAISE EXCEPTION 'BG-LU-06: sort_order invalid' USING ERRCODE = 'check_violation';
END IF;
-- updated_at auto
NEW.updated_at := now();
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_tac_birth_gate_lu
BEFORE INSERT OR UPDATE
ON p9_g6_dryrun.tac_logical_unit
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_birth_gate_lu();
6.3 fn_tac_birth_gate_uv (BG-UV-01→06 skeleton + behavior-critical)
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_birth_gate_uv()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
DECLARE
v_section_type TEXT;
v_desc_required BOOLEAN;
v_body_required BOOLEAN;
v_bg_uv_02_mode TEXT;
v_bg_uv_03_mode TEXT;
BEGIN
-- Get section_type config from logical_unit + section_type_vocab
SELECT lu.section_type, st.description_required, st.body_required
INTO v_section_type, v_desc_required, v_body_required
FROM p9_g6_dryrun.tac_logical_unit lu
JOIN p9_g6_dryrun.tac_section_type_vocab st ON st.code = lu.section_type
WHERE lu.id = NEW.logical_unit_id;
IF v_section_type IS NULL THEN
RAISE EXCEPTION 'BG-UV: logical_unit_id % không tồn tại', NEW.logical_unit_id
USING ERRCODE = 'foreign_key_violation';
END IF;
-- BG-UV-01: title present (covered by NOT NULL, redundant)
IF NEW.title IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.title)) = 0 THEN
RAISE EXCEPTION 'BG-UV-01: title rỗng' USING ERRCODE = 'not_null_violation';
END IF;
-- BG-UV-02: description per config (mode tiered từ tac_birth_gate_config)
SELECT mode INTO v_bg_uv_02_mode
FROM p9_g6_dryrun.tac_birth_gate_config
WHERE checker_id = 'BG-UV-02';
v_bg_uv_02_mode := coalesce(v_bg_uv_02_mode, 'block');
IF v_desc_required AND (NEW.description IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.description)) = 0) THEN
IF v_bg_uv_02_mode = 'block' THEN
RAISE EXCEPTION 'BG-UV-02: description required cho section_type %', v_section_type
USING ERRCODE = 'not_null_violation';
ELSE
RAISE WARNING 'BG-UV-02: description thiếu (warn mode)';
END IF;
END IF;
-- BG-UV-03: body per config
SELECT mode INTO v_bg_uv_03_mode
FROM p9_g6_dryrun.tac_birth_gate_config
WHERE checker_id = 'BG-UV-03';
v_bg_uv_03_mode := coalesce(v_bg_uv_03_mode, 'warn');
IF v_body_required AND (NEW.body IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.body)) = 0) THEN
IF v_bg_uv_03_mode = 'block' THEN
RAISE EXCEPTION 'BG-UV-03: body required cho section_type %', v_section_type
USING ERRCODE = 'not_null_violation';
ELSE
RAISE WARNING 'BG-UV-03: body thiếu (warn mode)';
END IF;
END IF;
-- BG-UV-04: required profile per section_type config (skeleton — chi tiết JSON Schema deferred)
-- TODO post-G6: validate content_profile against JSON schema in section_type config
-- BG-UV-05: length_flag set by fn_tac_uv_compute_derived (separate trigger)
-- BG-UV-06: provenance valid (verify FAC-PROV vocab — deferred behavior test post-G6)
IF NEW.provenance NOT IN ('PROV-AI', 'PROV-HUMAN', 'PROV-DOT') THEN
RAISE WARNING 'BG-UV-06: provenance % chưa map FAC-PROV vocab', NEW.provenance;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_tac_birth_gate_uv
BEFORE INSERT
ON p9_g6_dryrun.tac_unit_version
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_birth_gate_uv();
6.4 fn_tac_enacted_immut (INV-ENACTED-IMMUT)
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_enacted_immut()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
BEGIN
-- Block UPDATE on body/title/description/content_profile khi lifecycle='enacted'
IF OLD.lifecycle_status = 'enacted' AND TG_OP = 'UPDATE' THEN
IF (NEW.body IS DISTINCT FROM OLD.body)
OR (NEW.title IS DISTINCT FROM OLD.title)
OR (NEW.description IS DISTINCT FROM OLD.description)
OR (NEW.content_profile IS DISTINCT FROM OLD.content_profile)
THEN
RAISE EXCEPTION 'INV-ENACTED-IMMUT: cannot modify body/title/description/content_profile of enacted unit_version %', OLD.id
USING ERRCODE = 'check_violation';
END IF;
END IF;
-- Block DELETE enacted
IF TG_OP = 'DELETE' AND OLD.lifecycle_status = 'enacted' THEN
RAISE EXCEPTION 'INV-ENACTED-IMMUT: cannot delete enacted unit_version %', OLD.id
USING ERRCODE = 'check_violation';
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$;
CREATE TRIGGER trg_tac_enacted_immut
BEFORE UPDATE OR DELETE
ON p9_g6_dryrun.tac_unit_version
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_enacted_immut();
6.5 fn_tac_pm_consistency (INV-PM-CONSISTENCY)
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_pm_consistency()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
DECLARE
v_uv_logical_unit_id UUID;
BEGIN
-- Verify unit_version.logical_unit_id = publication_member.logical_unit_id
SELECT logical_unit_id INTO v_uv_logical_unit_id
FROM p9_g6_dryrun.tac_unit_version
WHERE id = NEW.unit_version_id;
IF v_uv_logical_unit_id IS NULL THEN
RAISE EXCEPTION 'INV-PM-CONSISTENCY: unit_version_id % không tồn tại', NEW.unit_version_id
USING ERRCODE = 'foreign_key_violation';
END IF;
IF v_uv_logical_unit_id != NEW.logical_unit_id THEN
RAISE EXCEPTION 'INV-PM-CONSISTENCY: pm.logical_unit_id (%) != uv.logical_unit_id (%)',
NEW.logical_unit_id, v_uv_logical_unit_id
USING ERRCODE = 'check_violation';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_tac_pm_consistency
BEFORE INSERT OR UPDATE
ON p9_g6_dryrun.tac_publication_member
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_pm_consistency();
6.6 fn_tac_pm_enacted_lock (INV-ENACTED-PUB)
CREATE OR REPLACE FUNCTION p9_g6_dryrun.fn_tac_pm_enacted_lock()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = p9_g6_dryrun, pg_catalog
AS $$
DECLARE
v_pub_lifecycle TEXT;
v_pub_id UUID;
BEGIN
-- Determine publication_id depending on operation
IF TG_OP IN ('INSERT', 'UPDATE') THEN
v_pub_id := NEW.publication_id;
ELSE -- DELETE
v_pub_id := OLD.publication_id;
END IF;
SELECT lifecycle_status INTO v_pub_lifecycle
FROM p9_g6_dryrun.tac_publication
WHERE id = v_pub_id;
-- Block all changes when publication = 'enacted'
IF v_pub_lifecycle = 'enacted' THEN
RAISE EXCEPTION 'INV-ENACTED-PUB: publication_member của publication % đã enacted, locked', v_pub_id
USING ERRCODE = 'check_violation';
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$;
CREATE TRIGGER trg_tac_pm_enacted_lock
BEFORE INSERT OR UPDATE OR DELETE
ON p9_g6_dryrun.tac_publication_member
FOR EACH ROW
EXECUTE FUNCTION p9_g6_dryrun.fn_tac_pm_enacted_lock();
6.7 Dependency note (v0.2 GPT R1#2)
KHÔNG cần pgcrypto extension. Tất cả function dùng built-in:
pg_catalog.sha256(bytea)— PG 11+, built-inpg_catalog.convert_to(text, encoding)— built-inpg_catalog.encode(bytea, format)— built-inpg_catalog.regexp_split_to_array(text, pattern)— built-inpg_catalog.array_length(array, dim)— built-inpg_catalog.length(text),pg_catalog.btrim(text)— built-ingen_random_uuid()— PG 13+, built-in trong default schema
Pre-flight PF-03 verify pg_catalog.sha256 tồn tại (PG 16 chắc chắn có). Nếu PG < 11 → STOP, escalate.
7. Seed plan + SHA-256
7.1 Seed strategy
Mỗi vocab table có 1 file seed .sql read-only. SHA-256 hash tính trước khi run, ghi vào seed-manifest.json. Re-run dry-run phải verify hash khớp.
7.2 Seed values (full per OD-G6-02 GPT R1 PASS)
seed-tac-lu-lifecycle.sql (3 rows):
INSERT INTO p9_g6_dryrun.tac_lu_lifecycle_vocab (code, name, description, sort_order) VALUES
('active', 'Hoạt động', 'Logical unit có ≥1 version enacted', 10),
('draft_only', 'Chỉ bản nháp', 'Chưa từng có version enacted', 20),
('retired', 'Đã rút', 'Quyết định retire qua change-set + APR', 30);
seed-tac-uv-lifecycle.sql (4 rows):
INSERT INTO p9_g6_dryrun.tac_uv_lifecycle_vocab (code, name, description, sort_order) VALUES
('draft', 'Bản nháp', 'Mới tạo, chưa approve, sửa tại chỗ', 10),
('enacted', 'Đã ban hành', 'Approve qua change-set + APR. Bất biến.', 20),
('superseded', 'Bị thay', 'Bị version mới enacted thay. Giữ audit.', 30),
('retired', 'Đã rút', 'Cascaded từ retire logical_unit', 40);
seed-tac-review-state.sql (5 rows):
INSERT INTO p9_g6_dryrun.tac_review_state_vocab (code, name, description, sort_order) VALUES
('unreviewed', 'Chưa rà soát', '', 10),
('in_review', 'Đang rà soát', '', 20),
('review_passed', 'Đã pass', '', 30),
('review_failed', 'Đã fail', '', 40),
('needs_re_review', 'Cần rà lại', '', 50);
seed-tac-pub-lifecycle.sql (4 rows):
INSERT INTO p9_g6_dryrun.tac_pub_lifecycle_vocab (code, name, description, sort_order) VALUES
('proposed', 'Đề xuất', 'Mutable, MAY reference draft', 10),
('enacted', 'Ban hành', 'Membership locked, immutable', 20),
('superseded', 'Bị thay', 'Version publication mới enacted', 30),
('retired', 'Đã rút', 'Quyết định governance', 40);
seed-tac-cs-lifecycle.sql (7 rows):
INSERT INTO p9_g6_dryrun.tac_cs_lifecycle_vocab (code, name, description, sort_order) VALUES
('draft', 'Nháp', '', 10),
('submitted', 'Đã gửi', 'Snapshot đóng băng (I9)', 20),
('review_passed', 'Reviewer pass', '', 30),
('approval_passed', 'APR quorum đạt', '', 40),
('enacted', 'Đã apply', '', 50),
('rejected', 'Đã từ chối', '', 60),
('withdrawn', 'Đã rút', '', 70);
seed-tac-section-type.sql (17 rows — full per C1A §6):
INSERT INTO p9_g6_dryrun.tac_section_type_vocab
(code, name, description, soft_limit_words, hard_limit_words, description_required, body_required) VALUES
('heading', 'Tiêu đề', 'Navigation heading', 100, 300, FALSE, FALSE),
('article', 'Điều', 'Đơn vị Điều của luật', 500, 1500, TRUE, TRUE),
('paragraph', 'Đoạn', 'Đoạn văn xuôi', 300, 1000, TRUE, TRUE),
('definition', 'Định nghĩa', 'Định nghĩa thuật ngữ', 200, 500, TRUE, TRUE),
('principle', 'Nguyên tắc', 'Nguyên tắc cốt lõi', 300, 800, TRUE, TRUE),
('rationale', 'Luận giải', 'Giải thích lý do', 400, 1200, TRUE, TRUE),
('process', 'Quy trình', 'Quy trình các bước', 500, 1500, TRUE, TRUE),
('technical_spec', 'Đặc tả kỹ thuật', 'Pseudo-DDL, schema', 1000, 3000, TRUE, TRUE),
('governance_process', 'Quy trình quản trị', 'Governance flow', 500, 1500, TRUE, TRUE),
('checklist', 'Danh sách kiểm', 'Checklist', 300, 1000, FALSE, TRUE),
('instruction_block', 'Khối hướng dẫn', 'Hướng dẫn dài', 1000, 3000, TRUE, TRUE),
('reference_mapping', 'Bảng tham chiếu', 'Crosswalk table', 500, 2000, FALSE, TRUE),
('matrix', 'Ma trận', 'Multi-dim table', 500, 2000, FALSE, TRUE),
('invariant_list', 'Danh mục bất biến', 'Invariants table', 500, 2000, FALSE, TRUE),
('open_decision_list', 'Danh mục câu hỏi mở', 'Open decisions table', 500, 2000, FALSE, TRUE),
('appendix', 'Phụ lục', 'Appendix dài', 2000, 5000, FALSE, TRUE),
('changelog', 'Lịch sử thay đổi', 'Changelog/patch log', 500, 2000, FALSE, TRUE);
seed-tac-publication-type.sql (10 rows — full per C1A §11.3):
INSERT INTO p9_g6_dryrun.tac_publication_type_vocab
(code, name, description, default_risk_tier) VALUES
('law', 'Luật', 'Văn bản pháp lý', 'highest'),
('policy', 'Chính sách', 'Chính sách công ty', 'high'),
('sop', 'Quy trình SOP', 'Standard Operating Procedure', 'high'),
('constitution', 'Hiến pháp', 'Hiến pháp kiến trúc', 'highest'),
('knowledge', 'Tri thức', 'Knowledge base article', 'medium'),
('design_note', 'Ghi chú thiết kế', 'Design note', 'medium'),
('report', 'Báo cáo', 'Report', 'low'),
('memo', 'Memo', 'Internal memo', 'low'),
('draft', 'Nháp', 'Working draft', 'low'),
('working', 'Đang làm việc', 'Working document', 'low');
seed-tac-birth-gate-config.sql (per P8 §2.3 tiered, 11 rows):
INSERT INTO p9_g6_dryrun.tac_birth_gate_config (checker_id, mode, enabled, rationale) VALUES
('BG-LU-02', 'block', TRUE, 'Hard invariant'),
('BG-LU-03', 'block', TRUE, 'Hard invariant'),
('BG-LU-04', 'block', TRUE, 'Hard invariant'),
('BG-LU-05', 'block', TRUE, 'Hard invariant'),
('BG-LU-06', 'block', TRUE, 'Hard invariant'),
('BG-UV-01', 'block', TRUE, 'Hard invariant'),
('BG-UV-02', 'block', TRUE, 'Required profile'),
('BG-UV-03', 'warn', TRUE, 'Quality, can grace'),
('BG-UV-04', 'block', TRUE, 'Required profile'),
('BG-UV-05', 'warn', TRUE, 'Quality (length)'),
('BG-UV-06', 'warn', TRUE, 'Quality (provenance)');
7.3 SHA-256 manifest
{
"seed_manifest_version": "0.2",
"generated_at": "2026-04-27T...",
"files": [
{"path": "seed-tac-lu-lifecycle.sql", "rows": 3, "sha256": "..."},
{"path": "seed-tac-uv-lifecycle.sql", "rows": 4, "sha256": "..."},
{"path": "seed-tac-review-state.sql", "rows": 5, "sha256": "..."},
{"path": "seed-tac-pub-lifecycle.sql", "rows": 4, "sha256": "..."},
{"path": "seed-tac-cs-lifecycle.sql", "rows": 7, "sha256": "..."},
{"path": "seed-tac-section-type.sql", "rows": 17, "sha256": "..."},
{"path": "seed-tac-publication-type.sql", "rows": 10, "sha256": "..."},
{"path": "seed-tac-birth-gate-config.sql", "rows": 11, "sha256": "..."}
],
"total_seed_rows": 61,
"kb_source": "P9-G6-source-extraction-note.md §4 + P5 §5.5/§5.6 + P8 §2.3"
}
Hash tính bằng sha256sum seed-tac-*.sql (Bash). Manifest commit vào git hoặc upload KB. Agent verify hash trước seed step.
8. Verification plan
8.1 Stage 1 — Compile verify (sau DDL run)
| # | Check | SQL | Expected |
|---|---|---|---|
| V1-01 | Schema tồn tại | SELECT 1 FROM information_schema.schemata WHERE schema_name='p9_g6_dryrun'; |
1 row |
| V1-02 | 14 tables tồn tại | SELECT table_name FROM information_schema.tables WHERE table_schema='p9_g6_dryrun' ORDER BY table_name; |
14 rows, names khớp danh sách §5 |
| V1-03 | All FK constraints exist | SELECT count(*) FROM information_schema.table_constraints WHERE table_schema='p9_g6_dryrun' AND constraint_type='FOREIGN KEY'; |
≥ 12 (theo §5.7) |
| V1-04 | All UNIQUE constraints exist | SELECT count(*) FROM information_schema.table_constraints WHERE table_schema='p9_g6_dryrun' AND constraint_type='UNIQUE'; |
≥ 2 (uv unique, pm unique, csm unique) |
| V1-05 | All CHECK constraints exist | SELECT count(*) FROM information_schema.table_constraints WHERE table_schema='p9_g6_dryrun' AND constraint_type='CHECK'; |
≥ 24 |
| V1-06 | All indexes exist | SELECT count(*) FROM pg_indexes WHERE schemaname='p9_g6_dryrun'; |
≥ 21 (gồm PK indexes auto) |
| V1-07 | 6 functions compile | SELECT proname FROM pg_proc WHERE pronamespace='p9_g6_dryrun'::regnamespace ORDER BY proname; |
6 rows: fn_tac_birth_gate_lu, fn_tac_birth_gate_uv, fn_tac_enacted_immut, fn_tac_pm_consistency, fn_tac_pm_enacted_lock, fn_tac_uv_compute_derived |
| V1-08 | 6 triggers attached | SELECT trigger_name FROM information_schema.triggers WHERE trigger_schema='p9_g6_dryrun' ORDER BY trigger_name; |
6+ trigger names (BEFORE/AFTER có thể tạo nhiều entries) |
| V1-09 | All functions có SECURITY DEFINER | SELECT proname, prosecdef FROM pg_proc WHERE pronamespace='p9_g6_dryrun'::regnamespace; |
6 rows, all prosecdef=true |
| V1-10 | All functions có search_path locked | SELECT proname, proconfig FROM pg_proc WHERE pronamespace='p9_g6_dryrun'::regnamespace; |
6 rows, proconfig contains 'search_path=p9_g6_dryrun, pg_catalog' |
8.2 Stage 2 — Seed verify (sau seed run)
| # | Check | SQL | Expected |
|---|---|---|---|
| V2-01 | tac_lu_lifecycle_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_lu_lifecycle_vocab; |
3 |
| V2-02 | tac_uv_lifecycle_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_uv_lifecycle_vocab; |
4 |
| V2-03 | tac_review_state_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_review_state_vocab; |
5 |
| V2-04 | tac_pub_lifecycle_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_pub_lifecycle_vocab; |
4 |
| V2-05 | tac_cs_lifecycle_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_cs_lifecycle_vocab; |
7 |
| V2-06 | tac_section_type_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_section_type_vocab; |
17 |
| V2-07 | tac_publication_type_vocab seed | SELECT count(*) FROM p9_g6_dryrun.tac_publication_type_vocab; |
10 |
| V2-08 | tac_birth_gate_config seed | SELECT count(*) FROM p9_g6_dryrun.tac_birth_gate_config; |
11 |
| V2-09 | Total seed rows | sum tất cả | 61 |
| V2-10 | SHA-256 manifest match | sha256sum seed-*.sql so với manifest |
Match all |
8.3 Stage 3 — Constraint behavior smoke (test 1 violation per type)
Test violation types để chứng minh constraint bind đúng:
| # | Test | SQL | Expected |
|---|---|---|---|
| V3-01 (UNIQUE) | Duplicate canonical_address | INSERT 2 logical_unit cùng canonical_address='D38-G6TEST-ROOT' |
First PASS, second FAIL with 23505 (unique_violation) |
| V3-02 (FK) | logical_unit với section_type không tồn tại | INSERT logical_unit với section_type='nonexistent' |
FAIL with 23503 (foreign_key_violation) |
| V3-03 (CHECK) | risk_tier ngoài enum | INSERT publication với risk_tier='invalid' |
FAIL with 23514 (check_violation) |
| V3-04 (NOT NULL) | logical_unit thiếu owner | INSERT logical_unit với owner=NULL |
FAIL with 23502 (not_null_violation) |
| V3-05 (Trigger BG-LU-01) | canonical_address không khớp regex | INSERT logical_unit với canonical_address='WRONG-FORMAT' |
FAIL with 23514 (raised by trigger) |
| V3-06 (Trigger BG-LU-03) | parent doc_code khác child | Setup parent doc='D38-A', INSERT child doc='D38-B' parent_id=parent.id | FAIL raised by trigger |
| V3-07 (Trigger PM-CONSISTENCY) | PM với uv.lu_id ≠ pm.lu_id | Setup 2 lu + 1 uv on lu1, INSERT pm với pm.lu_id=lu2 + pm.uv_id=uv1 | FAIL raised by trigger |
| V3-08 (Cleanup) | DELETE all test rows | DELETE FROM p9_g6_dryrun.tac_publication_member; DELETE FROM tac_unit_version; DELETE FROM tac_logical_unit; DELETE FROM tac_publication; |
All deleted, 0 rows remaining |
Lưu ý: V3 chỉ là smoke test. Behavior test đầy đủ post-G6 (trên P9 production hoặc next dry-run round).
8.4 Stage 4 — Production isolation verify
| # | Check | SQL | Expected |
|---|---|---|---|
| V4-01 | Production public.* tac_* objects vẫn whitelisted only (mở rộng v0.2 GPT R1#4) |
Re-run PF-02 query (§4.1.1) | total_violations=0 |
| V4-02 | Production public.fn_tac_* chỉ có whitelisted |
SELECT count(*) FROM pg_proc WHERE pronamespace='public'::regnamespace AND proname LIKE 'fn_tac\_%' ESCAPE '\'; |
1 (chỉ fn_tac_log_checker_issue) |
| V4-03 | taxonomy_facets không thay đổi |
Directus API GET facets count | 10 (FAC-01→09 + FAC-PROV) |
| V4-04 | dot_tools không thay đổi |
Directus API GET DOT-TAC-* count | 19 (ids 971-989) |
| V4-05 | system_issues không thêm rows từ G6 |
SELECT count(*) FROM public.system_issues WHERE created_at > '${run_start_ts}' AND details::text LIKE '%p9_g6_dryrun%'; |
0 |
| V4-06 | dot_action_log không thêm rows từ G6 (v0.2 GPT R1#3) |
SELECT count(*) FROM public.dot_action_log WHERE created_at > '${run_start_ts}' AND (action_metadata::text LIKE '%p9_g6_dryrun%' OR tool_code LIKE 'DOT-TAC-SCHEMA-%'); |
0 — G6 KHÔNG ghi audit table |
| V4-07 | Backup integrity sau G6 | rclone check GDrive backup tồn tại | OK |
9. Rollback plan
9.1 Rollback steps
-- Step 1: Verify current state
SELECT count(*) FROM information_schema.tables WHERE table_schema='p9_g6_dryrun';
-- Expected: 14
-- Step 2: DROP SCHEMA CASCADE
DROP SCHEMA IF EXISTS p9_g6_dryrun CASCADE;
-- Step 3: Verify zero residue
SELECT count(*) FROM information_schema.schemata WHERE schema_name='p9_g6_dryrun';
-- Expected: 0
SELECT count(*) FROM information_schema.tables WHERE table_schema='p9_g6_dryrun';
-- Expected: 0
SELECT count(*) FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'p9_g6_dryrun';
-- Expected: 0
-- Step 4: Verify production unchanged
-- (re-run V4 verification)
9.2 Rollback PASS criteria
| # | Criterion | Expected |
|---|---|---|
| R-01 | Schema p9_g6_dryrun không tồn tại |
0 rows in pg_namespace |
| R-02 | 0 tables trong schema | 0 |
| R-03 | 0 functions trong schema | 0 |
| R-04 | 0 triggers liên quan | 0 |
| R-05 | Production public.* tac_* objects vẫn whitelisted only (PF-02 expanded) |
total_violations=0 |
| R-06 | Production public.fn_tac_log_checker_issue vẫn nguyên |
1 |
| R-07 | taxonomy_facets count vẫn = 10 |
10 |
| R-08 | dot_tools DOT-TAC-* vẫn = 19 |
19 |
9.3 Rollback timing
Rollback chạy SAU verification Stage 1+2+3+4 và User/GPT confirm G6 PASS. Hoặc rollback sớm nếu BẤT KỲ verification stage FAIL.
Nguyên tắc: Schema p9_g6_dryrun KHÔNG được tồn tại quá 24h trên production server. Sau verification → rollback ngay → archive log.
10. Hard exclusions (CẤM tuyệt đối trong G6 execution)
Theo §2.2 + handoff §3.3 + GPT R1 chỉ đạo:
| # | Hard exclusion | Rationale |
|---|---|---|
| HE-01 | KHÔNG CREATE/ALTER/DROP public.tac_* (mọi loại object) |
Production isolation |
| HE-02 | KHÔNG CREATE/MODIFY Directus roles |
Thuộc G8 |
| HE-03 | KHÔNG CREATE/MODIFY Directus collections |
Thuộc G8 + P9 |
| HE-04 | KHÔNG activate cron jobs | Thuộc P9 production |
| HE-05 | KHÔNG INSERT/UPDATE/DELETE taxonomy_labels |
Đ24 mutation |
| HE-06 | KHÔNG INSERT/UPDATE/DELETE entity_labels |
Đ24 mutation |
| HE-07 | KHÔNG INSERT system_issues (kể cả qua wrapper) |
G7 đã PASS, G6 không test path này |
| HE-08 | KHÔNG INSERT dot_action_log (v0.2 GPT R1#3) |
Production audit table; default G6 chỉ KB markdown + local artifacts |
| HE-09 | KHÔNG cleanup _dot_origin |
Out of scope |
| HE-10 | KHÔNG behavior test functions/triggers ngoài V3 smoke | Defer post-G6 |
| HE-11 | KHÔNG DROP/ALTER public.fn_tac_log_checker_issue |
E-R3 production artifact |
| HE-12 | KHÔNG modify dot_tools / taxonomy_facets |
E5/E7 production artifacts |
| HE-13 | KHÔNG modify GSM secrets | Đ33 §14 |
| HE-14 | KHÔNG hardcode -U directus trong DDL script |
§0-AU + bài học S183 |
| HE-15 | KHÔNG unqualified DDL | Bài học S183 schema-qualify |
| HE-16 | KHÔNG bypass pre-flight checks | AP-DRY-RUN-FK |
| HE-17 | KHÔNG CREATE EXTENSION pgcrypto (v0.2 GPT R1#2) |
DB-level mutation; G6 dùng built-in pg_catalog.sha256() thay digest() |
11. Bài học S183 áp dụng (rà 13 NT HP v4.6.2)
11.1 Schema-qualify (S183 #2)
| Áp dụng | Hành động |
|---|---|
| Mọi CREATE TABLE | p9_g6_dryrun.tac_* |
| Mọi CREATE FUNCTION | p9_g6_dryrun.fn_tac_* |
| Mọi CREATE TRIGGER | ON p9_g6_dryrun.tac_* |
| Mọi DROP statement | p9_g6_dryrun.{object} |
| Mọi verify SELECT | p9_g6_dryrun.{table} hoặc WHERE table_schema='p9_g6_dryrun' |
| Mọi function call | pg_catalog.sha256, pg_catalog.encode, etc. |
SET search_path |
TO p9_g6_dryrun, pg_catalog (loại public) |
11.2 §0-AU không hardcode (S183 #1, v0.2 patched)
| Áp dụng | Hành động |
|---|---|
| Connection identity | psql "host=$DB_HOST port=$DB_PORT user=$DB_USER dbname=$DB_NAME" từ .env |
| KHÔNG | psql -U directus ... hardcoded |
| Verify identity sau connect | SELECT current_user, current_database(); |
| Permission check | Verify user có CREATE SCHEMA permission, không bắt user phải là workflow_admin cụ thể (v0.2 GPT R1#5 OD-G6-09) |
11.3 AP-DRY-RUN-FK (S183 #3)
| Áp dụng | Hành động |
|---|---|
| Pre-flight §4.2 verify FK vocabulary | Trước khi tạo CORE tables, verify vocab tables seed thành công và values khớp expected |
| FK reference verify | Sau seed, kiểm count vocab table = expected count (3/4/5/4/7/17/10/11) |
| Constraint smoke V3-02 | Test FK violation explicitly để chứng minh FK bind |
11.4 AP-APR-SCOPE (S183 #4)
| Áp dụng | Hành động |
|---|---|
| Scope lock | G6 = isolated schema dry-run, không mở rộng sang production/Directus |
| Scope verify trong execution prompt | Liệt kê hard exclusions §10 trong prompt |
| Verify post-execution | Stage 4 Production Isolation verify (V4-01→V4-07, mở rộng v0.2) |
11.5 13 NT (HP v4.6.2) rà 3 tầng (Đ43 amend)
| NT | Tầng 1 (code bash/python) | Tầng 2 (SQL DDL) | Tầng 3 (SQL data) |
|---|---|---|---|
| NT1 PG nền tảng | DDL script chạy psql 16 | 14 tables + 6 functions PG-native | 61 vocab seed rows |
| NT2 Cơ chế máy | psql exit code check | CHECK + FK + trigger compile | Constraint smoke V3 |
| NT4 Config not hardcode | .env cho connection |
FK vocab thay CHECK enum (P8 §1.1) | Vocab values seed từ KB |
| NT8 Scale | Index design | 21 indexes (GIN + partial hợp lệ + B-tree) | — |
| NT11 Khai tối thiểu | — | 14 tables faithful P5, không over-design | Seed minimal + đủ |
| NT13 PG First | DDL only PG | Trigger PG-native, function PL/pgSQL, built-in sha256() không cần extension | — |
| Đ24 | — | FK to vocab production via CHECK constraint format | — |
| Đ32 | — | change_set hooks | — |
| Đ33 | E1 exception path | E1 cho schema isolated, KHÔNG CREATE EXTENSION (v0.2) | E1 bootstrap seed |
| Đ35 | — | DOT-TAC-SCHEMA-ENSURE invocation pattern | — |
12. Đối chiếu luật (constitutional check đầy đủ)
| Nguyên tắc / Luật | Verdict G6 v0.2 | Evidence |
|---|---|---|
| NT1 PG nền tảng | PASS | DDL pure PostgreSQL 16, schema isolated trên cùng instance |
| NT2 Cơ chế máy | PASS | 24+ CHECK + 12+ FK + 6 trigger = máy enforce |
| NT4 Config not hardcode | PASS | FK vocab + birth_gate_config; seed từ KB không hardcode trong code |
| NT5 Lỗi=cơ hội vàng | PASS | Pre-flight + verification capture lỗi sớm; bài học S183 + GPT R1 baked vào |
| NT8 Scale | PASS | 21 indexes (GIN + partial hợp lệ + B-tree) per P5 design |
| NT11 Khai tối thiểu | PASS | 14 tables faithful P5, không tạo registry song song |
| NT13 PG First | PASS | Mọi enforcement = PG trigger + constraint + built-in sha256(), không app-level workaround |
| NT-DB-CONSTRAINT-FIRST (S176B) | PASS | UNIQUE, FK, CHECK, NOT NULL bind ở DB layer |
| Đ24 Label Law | PASS | Facets E5 đã sẵn (FAC-07/08/09); G6 không ghi entity_labels |
| Đ32 APR | PASS | change_set hooks; G6 không enact thật |
| Đ33 PG Law | PASS | E1 exception cho DDL trong schema isolated; KHÔNG CREATE EXTENSION (v0.2 GPT R1#2); KHÔNG ghi dot_action_log (v0.2 GPT R1#3) |
| Đ35 DOT Law | PASS | DOTs E7 đã registered; G6 = SCHEMA-ENSURE (B) + SCHEMA-VERIFY (A) paired; action log = KB markdown only (v0.2 GPT R1#3) |
| Đ38 Text as Code | PASS | Implements P5 schema design + P8 hardening |
| Đ43 v1.2 | PASS | Phase pattern P0 (snapshot) → P1 (DDL) → P2 (seed) → P3 (verify) → P4 (smoke) → P5 (rollback) → P6 (report) |
| §0-AU không hardcode | PASS | .env + $DB_USER; verify permission, không bắt fixed user (v0.2 GPT R1#5) |
| §0-AY không register=vô hình | N/A | G6 schema = transient test, ROLLBACK xóa sạch |
| §0-AF evidence production URL | PASS | psql output + verify queries làm evidence + KB markdown report |
| 5-GATE (S176B) | PASS | G1 đọc luật ✅; G2 COUNT trước WHERE ✅; G3 đối chiếu P5+P8+Đ33 ✅; G4 từ chuẩn ✅; G5 không chắc dừng ✅ |
| NGUYÊN TẮC LUẬT TRƯỚC | PASS | 4 câu hỏi: (1) Có luật ✅ P5/P8; (2) Đủ ✅ với GPT R1 chốt 10 OD; (3) DOT làm theo ✅ DOT-TAC-SCHEMA-*; (4) Không xung đột ✅ |
| Tuyên ngôn thiết kế ①②③ | PASS | ① Tạm (G6 = transient); ② Cơ hội nhầm: hard exclusions HE-01→17; ③ Lỗi=vàng: verify đầy đủ |
13. Risk register
| # | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R-01 | Agent ghi nhầm vào public.tac_* thay vì p9_g6_dryrun.* |
Low | Cao | (1) Mọi DDL schema-qualify; (2) SET search_path loại public; (3) PF-02 expanded verify production tac_* objects = 0 trước/sau (v0.2); (4) HE-15 explicit |
| R-02 | DROP SCHEMA CASCADE xóa nhầm production | Very Low | Cao | DROP SCHEMA chỉ target p9_g6_dryrun, schema name không trùng production |
| R-03 | N/A | N/A | Không cần pgcrypto. PF-03 verify pg_catalog.sha256 (PG 11+ chắc chắn có trên PG 16) | |
| R-04 | Seed SHA-256 mismatch | Low | Trung | Manifest commit vào git trước run; agent verify hash trước seed |
| R-05 | Trigger ATTACH fail vì function không tồn tại | Low | Trung | Order trong DDL script: tables → functions → triggers |
| R-06 | TRIGGER-GUARD block CREATE SCHEMA | Low | Cao | PF-08 verify guard mode informational; nếu BLOCK → escalate |
| R-07 | Rollback bị block (orphan FK constraint) | Very Low | Trung | DROP SCHEMA CASCADE handles; verify R-01→R-08 sau rollback |
| R-08 | Connection identity sai (current_user permission insufficient) | Low | Trung | PF-09 verify trước run, dựa permission không phải user name (v0.2) |
| R-09 | Disk space đủ cho schema | Very Low | Thấp | 14 empty tables + 61 seed rows ≈ <1MB |
| R-10 | Concurrent agent chạy cùng lúc | Very Low | Trung | Single-instance agent, locking script không cần |
| R-11 | Public schema có tac_ type/sequence/view residue chưa được PF-02 cũ catch* | Low | Cao | PF-02 expanded v0.2 GPT R1#4 check tables/views/sequences/types/triggers/functions — gộp 1 query UNION ALL |
| R-12 | Dot_action_log accidental write | Very Low | Trung | HE-08 v0.2 explicit ban, V4-06 verify post-execution |
14. Open decisions — RESOLVED (GPT R1, v0.2)
| Code | Câu hỏi | Decision (GPT R1) | Status |
|---|---|---|---|
| OD-G6-01 | Function body: skeleton hay full logic? | Skeleton + behavior-critical logic (như §6.1–6.6 với RAISE EXCEPTION, FK lookup thật) | ✅ RESOLVED |
| OD-G6-02 | Vocab seed: minimal hay full? | Full 61 rows | ✅ RESOLVED |
| OD-G6-03 | Constraint smoke: chỉ exist hay test violation? | Cả hai — V1 verify exist + V3 test violation | ✅ RESOLVED |
| OD-G6-04 | Rollback verify: count residue hay schema check? | Cả hai — DROP CASCADE + R-01→R-08 | ✅ RESOLVED |
| OD-G6-05 | Action log path | KB markdown report + local artifacts only (KHÔNG dot_action_log) |
✅ RESOLVED v0.2 GPT R1#3 |
| OD-G6-06 | Agent assignment | Claude CLI / Claude Code style — DDL phức tạp, plan-then-execute | ✅ RESOLVED |
| OD-G6-07 | pgcrypto handling | Verify existing built-in pg_catalog.sha256 (PG 11+) — KHÔNG cần pgcrypto. KHÔNG auto CREATE EXTENSION. Nếu PG version quá cũ → STOP |
✅ RESOLVED v0.2 GPT R1#2 (alternative chấp nhận) |
| OD-G6-08 | Partial index idx_tac_pm_pub_enacted |
Bỏ hẳn (cả CREATE và DROP). Note: optimization deferred, enacted lock enforce bằng trigger | ✅ RESOLVED v0.2 GPT R1#1 |
| OD-G6-09 | Connection user | Dùng .env, verify actor có CREATE SCHEMA permission. KHÔNG hardcode chỉ workflow_admin |
✅ RESOLVED v0.2 GPT R1#5 |
| OD-G6-10 | Run timing | Off-peak preferred, không bắt buộc nếu pre-flight + backup PASS | ✅ RESOLVED |
Tất cả 10 OD đã RESOLVED. Không còn OD pending block execution prompt.
15. PASS / FAIL criteria
15.1 G6 PASS
| # | Criterion | Verify by |
|---|---|---|
| 1 | All 10 pre-flight checks PASS (PF-01→PF-10) — bao gồm PF-02 expanded + PF-03 sha256 built-in | §4.1 |
| 2 | Schema p9_g6_dryrun created |
V1-01 |
| 3 | 14 tables compile + match P8 §1.4 | V1-02 |
| 4 | All FK/UNIQUE/CHECK constraints bind | V1-03→V1-05 |
| 5 | All indexes (≥21) created | V1-06 |
| 6 | 6 functions compile, all SECURITY DEFINER + search_path locked | V1-07/V1-09/V1-10 |
| 7 | 6 triggers attached | V1-08 |
| 8 | Seed 61 rows total, SHA-256 manifest match | V2-01→V2-10 |
| 9 | Constraint smoke V3 (8 sub-tests) all behavior expected | V3-01→V3-08 |
| 10 | Production isolation verify (V4-01→V4-07, expanded v0.2) all PASS | V4 |
| 11 | Rollback DROP SCHEMA CASCADE → 0 residue (R-01→R-08) | §9.2 |
| 12 | Action log markdown report uploaded KB (KHÔNG dot_action_log writes) | OD-G6-05 v0.2 |
| 13 | Hard exclusions HE-01→17 không vi phạm (verify production audit) | V4 |
| 14 | GPT review PASS | Independent |
| 15 | User authorize G6 PASS → G8 | Final gate |
15.2 G6 FAIL conditions
| # | Condition | Severity |
|---|---|---|
| F-01 | Pre-flight ANY fail | STOP, không proceed |
| F-02 | DDL compile error (cú pháp/FK/CHECK invalid) | STOP, fix design, re-design |
| F-03 | Function PL/pgSQL parse error | STOP, fix function, re-run |
| F-04 | Seed SHA-256 mismatch | STOP, regen seed, re-hash |
| F-05 | Constraint smoke V3 expected violation không xảy ra | FAIL, indicates constraint không bind đúng |
| F-06 | V4 production isolation fail (production tac_* objects xuất hiện ngoài whitelist) | CRITICAL, immediate rollback + audit |
| F-07 | Rollback không clean (residue > 0) | CRITICAL, manual cleanup + post-mortem |
| F-08 | Hard exclusion violation detected | CRITICAL, immediate rollback + escalate |
| F-09 | dot_action_log có rows từ G6 run (V4-06 fail) (v0.2) | CRITICAL — vi phạm scope, immediate audit |
15.3 Abort conditions
| # | Condition | Action |
|---|---|---|
| A-01 | Production data touched | Immediate rollback + escalate User |
| A-02 | TRIGGER-GUARD blocks unexpectedly | Stop, escalate |
| A-03 | Backup không tươi (PF-07 fail) | Stop, run backup, retry |
| A-04 | Connection permission insufficient (PF-09 fail) | Stop, fix .env or escalate, retry |
| A-05 | Agent runaway loop / infinite trigger | Kill connection, rollback, post-mortem |
| A-06 | PG version < 11 (sha256 built-in absent, PF-03 fail) (v0.2) | Stop, escalate — PG upgrade required, không auto-fix |
16. Sequence post-G6 PASS
| Gate | Mục tiêu | Dependency |
|---|---|---|
| G6 PASS | Migration dry-run verified | Current package |
| G8 Directus roles | Tạo tac-agent + tac-admin roles + tokens GSM |
G6 PASS (cần biết schema để gán permissions) |
| G11 User approval | Final go/no-go cho P9 production migration | G6 + G8 PASS, all 12/12 gates |
| P9 production migration | Apply 14 tables + 6 functions + seed + collections + roles + DOTs activate vào public.tac_* |
G11 PASS |
Lưu ý: Sau G6 PASS, schema p9_g6_dryrun đã DROP. G8 sẽ chạy trên production public.tac_* SAU P9 migration thật, KHÔNG trên schema isolated nữa. G8 thiết kế gate riêng — không phải phần của G6 package này.
17. Patch log
v0.1 (S184, 2026-04-27)
- Initial draft sau khi đọc đầy đủ P5/P5b/P6/P7/P8/Tier 3 Readiness
- Schema-qualify mọi nơi (
p9_g6_dryrun.*) - 14 tables faithful P8 §1.4
- 6 functions skeleton compile-only
- 61 seed rows với SHA-256 manifest
- Pre-flight 10 + FK vocab verify
- Verification 4 stages
- Risk register 10 items, OD 10 items
- Constitutional check 19 luật/nguyên tắc
- Bài học S183 baked: schema-qualify, .env, FK pre-check, scope lock
v0.2 (S184, 2026-04-27) — GPT R1 patches (6 fixes)
| # | Patch | Áp dụng | Loại |
|---|---|---|---|
| 1 | Bỏ invalid partial index idx_tac_pm_pub_enacted ... WHERE EXISTS (...) |
§5.5 D4: bỏ cả CREATE và DROP, thêm note enforce trigger | GPT R1#1 |
| 2 | Pgcrypto/digest replacement | §6.1 chuyển sang pg_catalog.sha256(convert_to(...)) built-in. PF-03 verify sha256. HE-17 cấm CREATE EXTENSION. R-03 RESOLVED. A-06 mới. Đề xuất alternative thay vì template + pre-flight discovery — chờ GPT R2 confirm. |
GPT R1#2 |
| 3 | Bỏ dot_action_log writes default G6 | §2.1 action log = KB markdown only. HE-08 explicit. V4-06 verify post-execution. F-09 critical fail. OD-G6-05 RESOLVED. | GPT R1#3 |
| 4 | PF-02 expanded từ chỉ tables/functions → tables/views/sequences/types/triggers/functions với UNION ALL query gộp | §4.1.1 SQL chi tiết. R-11 mitigation. V4-01 expanded. | GPT R1#4 |
| 5 | OD chốt — chuyển 10 OD từ Pending sang Resolved | §14 đầy đủ. PASS criterion #14 cập nhật. | GPT R1#5 |
| 6 | Index revision note (acknowledge, không patch) | Note: tương lai dùng update_document thay delete+upload. Không blocker. |
GPT R1#6 |
Đề xuất alternative cần GPT R2 confirm: Patch 2 chuyển từ pgcrypto/digest pattern sang built-in pg_catalog.sha256() — clean hơn (không cần extension, không cần discovery). Nếu GPT R2 reject → revert sang template {{EXT_SCHEMA}}.digest(...) + pre-flight pgcrypto discovery + STOP if absent.
Patch dự kiến v0.3 (sau GPT R2)
- Pending GPT R2 review feedback
18. PASS criteria cho package này (review checklist v0.2)
| # | Criterion | Status |
|---|---|---|
| 1 | 14 tables full DDL trong p9_g6_dryrun.* | ✅ §5 |
| 2 | 6 functions/triggers full skeleton + behavior-critical | ✅ §6 |
| 3 | 61 seed rows với KB source | ✅ §7 |
| 4 | Pre-flight 10 checks (PF-02 expanded, PF-03 sha256) | ✅ §4 |
| 5 | Verification 4 stages (V4 expanded với V4-06 dot_action_log) | ✅ §8 |
| 6 | Rollback plan + verify | ✅ §9 |
| 7 | 17 hard exclusions (thêm HE-08 dot_action_log, HE-17 pgcrypto) | ✅ §10 |
| 8 | Bài học S183 áp dụng (5 items + 13 NT 3 tầng) | ✅ §11 |
| 9 | Constitutional check 19 nguyên tắc | ✅ §12 |
| 10 | Risk register 12 items (R-11 expanded check, R-12 dot_action_log) | ✅ §13 |
| 11 | 10 OD RESOLVED (GPT R1) — không pending blocker | ✅ §14 |
| 12 | PASS/FAIL/Abort criteria (F-09 dot_action_log critical, A-06 PG version) | ✅ §15 |
| 13 | Sequence post-G6 | ✅ §16 |
| 14 | Schema-qualify mọi DDL | ✅ §3.2/§5 |
| 15 | Không hardcode credentials | ✅ §11.2 |
| 16 | Hard exclusions explicit | ✅ §10 |
| 17 | Package = plan/DDL design, không phải execution prompt | ✅ §1 cảnh báo |
| 18 | 6 GPT R1 patches áp dụng | ✅ §17 v0.2 patch log |
| 19 | Invalid partial index removed | ✅ §5.5 D4 |
| 20 | Pgcrypto avoided via built-in sha256 | ✅ §6.1 + §6.7 |
| 21 | Dot_action_log excluded | ✅ §10 HE-08 + V4-06 |
| 22 | Public conflict check expanded | ✅ §4.1.1 |
P9 G6 Migration Dry-Run Package v0.2 | DRAFT | S184 | 2026-04-27 | Opus 4.7 Inputs: P5 v0.2, P5b v0.2, P6 v0.2, P7 v0.2, P8 v0.4, Tier 3 Readiness, source-extraction-note v0.1, GPT R1 review (6 patches) Đề xuất alternative trong patch 2 chờ GPT R2 confirm. Sau GPT R2 PASS → User authorize → soạn execution prompt riêng giao agent thực thi.